claude-overnight 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- claude_overnight-0.1.0/.claude-plugin/marketplace.json +11 -0
- claude_overnight-0.1.0/.claude-plugin/plugin.json +9 -0
- claude_overnight-0.1.0/.github/workflows/ci.yml +20 -0
- claude_overnight-0.1.0/.github/workflows/publish.yml +18 -0
- claude_overnight-0.1.0/.gitignore +12 -0
- claude_overnight-0.1.0/LICENSE +21 -0
- claude_overnight-0.1.0/PKG-INFO +129 -0
- claude_overnight-0.1.0/README.md +108 -0
- claude_overnight-0.1.0/assets/demo.gif +0 -0
- claude_overnight-0.1.0/commands/queue.md +12 -0
- claude_overnight-0.1.0/docs/configuration.md +57 -0
- claude_overnight-0.1.0/docs/design.md +46 -0
- claude_overnight-0.1.0/docs/how-it-works.md +72 -0
- claude_overnight-0.1.0/pyproject.toml +37 -0
- claude_overnight-0.1.0/src/overnight/__init__.py +3 -0
- claude_overnight-0.1.0/src/overnight/cli.py +154 -0
- claude_overnight-0.1.0/src/overnight/config.py +88 -0
- claude_overnight-0.1.0/src/overnight/install.py +76 -0
- claude_overnight-0.1.0/src/overnight/limits.py +125 -0
- claude_overnight-0.1.0/src/overnight/notify.py +18 -0
- claude_overnight-0.1.0/src/overnight/paths.py +37 -0
- claude_overnight-0.1.0/src/overnight/runner.py +196 -0
- claude_overnight-0.1.0/src/overnight/store.py +95 -0
- claude_overnight-0.1.0/tests/conftest.py +8 -0
- claude_overnight-0.1.0/tests/test_config.py +48 -0
- claude_overnight-0.1.0/tests/test_limits.py +44 -0
- claude_overnight-0.1.0/tests/test_runner.py +151 -0
- claude_overnight-0.1.0/tests/test_store.py +42 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "claude-overnight",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Queue research questions with /queue; they run when your usage limits reset overnight. Requires the claude-overnight CLI (uv tool install claude-overnight).",
|
|
5
|
+
"author": { "name": "Rohan Richard" },
|
|
6
|
+
"homepage": "https://github.com/rohanprichard/claude-overnight",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"keywords": ["queue", "rate-limits", "overnight", "research"]
|
|
9
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: macos-latest
|
|
11
|
+
strategy:
|
|
12
|
+
matrix:
|
|
13
|
+
python-version: ["3.11", "3.12", "3.13"]
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
- uses: astral-sh/setup-uv@v5
|
|
17
|
+
with:
|
|
18
|
+
python-version: ${{ matrix.python-version }}
|
|
19
|
+
- run: uv sync
|
|
20
|
+
- run: uv run pytest -q
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
publish:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
environment: pypi
|
|
12
|
+
permissions:
|
|
13
|
+
id-token: write
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
- uses: astral-sh/setup-uv@v5
|
|
17
|
+
- run: uv build
|
|
18
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Rohan Richard
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: claude-overnight
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Queue research questions all day. Wake up to answers. Puts your idle Claude Code quota to work while you sleep.
|
|
5
|
+
Project-URL: Homepage, https://github.com/rohanprichard/claude-overnight
|
|
6
|
+
Project-URL: Issues, https://github.com/rohanprichard/claude-overnight/issues
|
|
7
|
+
Author: Rohan Richard
|
|
8
|
+
License: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: automation,claude,claude-code,queue,rate-limits
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: MacOS
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Software Development :: Build Tools
|
|
19
|
+
Requires-Python: >=3.11
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# claude-overnight
|
|
23
|
+
|
|
24
|
+
**Queue research questions all day. Wake up to answers.**
|
|
25
|
+
|
|
26
|
+

|
|
27
|
+
|
|
28
|
+
Your Claude Code weekly limit quietly expires while you sleep — if you hit the 5-hour cap every day, you still leave weekly quota on the table every single night. `claude-overnight` puts that idle quota to work: queue questions with `/queue` during the day, and a scheduler runs them headlessly inside your configured night window, when your limits have reset. You wake up to a folder of markdown research reports and a morning digest.
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
you (2:14 pm) ──▶ /queue what are the tradeoffs of CRDTs vs OT for a collab editor?
|
|
32
|
+
you (5:40 pm) ──▶ /queue compare litestream vs. postgres logical replication for a side project
|
|
33
|
+
|
|
34
|
+
💤 3:00 am — limits reset, window open, batch runs
|
|
35
|
+
|
|
36
|
+
you (8:05 am) ──▶ ~/.overnight/results/index.md
|
|
37
|
+
✅ tradeoffs of CRDTs vs OT — crdts-vs-ot.md
|
|
38
|
+
✅ litestream vs postgres replication — litestream-vs-postgres.md
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Why
|
|
42
|
+
|
|
43
|
+
- **Limit-aware, not just time-aware.** Cron can run Claude at 3am, but it doesn't know whether your 5-hour window is fresh or your weekly cap is nearly gone. `claude-overnight` checks both before starting and *between every job*, and stops at a configurable threshold so you wake up with quota left for actual work.
|
|
44
|
+
- **Runs on your subscription, unattended.** Jobs execute through `claude -p` (headless mode) with tools restricted to web search — no API key, no extra cost.
|
|
45
|
+
- **Zero dependencies.** Pure Python standard library. `uv tool install claude-overnight` and you're done.
|
|
46
|
+
|
|
47
|
+
## Install
|
|
48
|
+
|
|
49
|
+
```sh
|
|
50
|
+
uv tool install claude-overnight # or: pipx install claude-overnight
|
|
51
|
+
overnight install # sets up the scheduler + /queue slash command
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
`overnight install` does two things:
|
|
55
|
+
|
|
56
|
+
1. Registers a **launchd agent** that wakes every 30 minutes, checks whether you're inside the night window and under the limit thresholds, and runs the queue if so.
|
|
57
|
+
2. Drops a **`/queue` slash command** into `~/.claude/commands/`, so you can queue questions without leaving Claude Code.
|
|
58
|
+
|
|
59
|
+
Requires macOS, Python 3.11+, and the [Claude Code](https://code.claude.com) CLI with a Pro/Max subscription.
|
|
60
|
+
|
|
61
|
+
## Use
|
|
62
|
+
|
|
63
|
+
```sh
|
|
64
|
+
# from anywhere
|
|
65
|
+
overnight add "how do sqlite WAL checkpoints actually work?"
|
|
66
|
+
|
|
67
|
+
# or inside any Claude Code session
|
|
68
|
+
/queue how do sqlite WAL checkpoints actually work?
|
|
69
|
+
|
|
70
|
+
overnight list # see the queue
|
|
71
|
+
overnight status # current 5h/weekly utilization + would-it-run-now
|
|
72
|
+
overnight run --force # run the batch right now, ignoring window/limits
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Results land in `~/.overnight/results/<date>/`, one markdown report per question, with a rolling `index.md` digest. A macOS notification fires when the batch finishes.
|
|
76
|
+
|
|
77
|
+
## Configure
|
|
78
|
+
|
|
79
|
+
`~/.overnight/config.toml`:
|
|
80
|
+
|
|
81
|
+
```toml
|
|
82
|
+
[window]
|
|
83
|
+
start = "01:00" # jobs only run between these local times
|
|
84
|
+
end = "07:00" # windows may cross midnight ("23:00" → "06:00")
|
|
85
|
+
|
|
86
|
+
[limits]
|
|
87
|
+
start_max_utilization = 20 # don't start if the 5h window is already >20% used
|
|
88
|
+
stop_utilization = 60 # stop the batch once 5h usage crosses 60%
|
|
89
|
+
weekly_max_utilization = 80 # never run if the weekly limit is >80% used
|
|
90
|
+
|
|
91
|
+
[run]
|
|
92
|
+
model = "sonnet"
|
|
93
|
+
job_timeout_minutes = 15
|
|
94
|
+
max_attempts = 2
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## How it works (there's no official API)
|
|
98
|
+
|
|
99
|
+
Claude Code doesn't expose a usage API — but it stores an OAuth token locally (macOS Keychain / `~/.claude/.credentials.json`), and the community discovered that `GET https://api.anthropic.com/api/oauth/usage` with that token returns your 5-hour and weekly utilization with reset timestamps. `claude-overnight` uses that to decide when it's safe to run.
|
|
100
|
+
|
|
101
|
+
Because the endpoint is **undocumented and could change**, everything degrades gracefully: if usage can't be read, the runner proceeds optimistically and detects limit errors from `claude -p` output instead — a job that hits the limit is requeued untouched for the next window, and the batch stops.
|
|
102
|
+
|
|
103
|
+
Full details in [docs/how-it-works.md](docs/how-it-works.md).
|
|
104
|
+
|
|
105
|
+
## Edge cases handled
|
|
106
|
+
|
|
107
|
+
- **Mac asleep at 3am** — launchd runs the missed tick on wake, so the batch runs when you open the lid, still before you start working.
|
|
108
|
+
- **One job eating the whole window** — per-job timeouts plus utilization re-checks between jobs.
|
|
109
|
+
- **Two runners racing** — lockfile with stale-lock recovery.
|
|
110
|
+
- **Headless Claude wanting to ask you something** — the prompt template instructs it to make reasonable assumptions and state them.
|
|
111
|
+
- **Weekly cap already blown** — the runner skips the batch and says why in `overnight status`.
|
|
112
|
+
|
|
113
|
+
## Uninstall
|
|
114
|
+
|
|
115
|
+
```sh
|
|
116
|
+
overnight uninstall # removes the launchd agent + slash command
|
|
117
|
+
uv tool uninstall claude-overnight
|
|
118
|
+
rm -rf ~/.overnight # queue, results, config
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Roadmap
|
|
122
|
+
|
|
123
|
+
- Repo-scoped coding jobs (run in a git worktree, results as branches)
|
|
124
|
+
- Linux (systemd timer) support
|
|
125
|
+
- "Quota saved this week" stats in `overnight status`
|
|
126
|
+
|
|
127
|
+
## License
|
|
128
|
+
|
|
129
|
+
MIT
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# claude-overnight
|
|
2
|
+
|
|
3
|
+
**Queue research questions all day. Wake up to answers.**
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
Your Claude Code weekly limit quietly expires while you sleep — if you hit the 5-hour cap every day, you still leave weekly quota on the table every single night. `claude-overnight` puts that idle quota to work: queue questions with `/queue` during the day, and a scheduler runs them headlessly inside your configured night window, when your limits have reset. You wake up to a folder of markdown research reports and a morning digest.
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
you (2:14 pm) ──▶ /queue what are the tradeoffs of CRDTs vs OT for a collab editor?
|
|
11
|
+
you (5:40 pm) ──▶ /queue compare litestream vs. postgres logical replication for a side project
|
|
12
|
+
|
|
13
|
+
💤 3:00 am — limits reset, window open, batch runs
|
|
14
|
+
|
|
15
|
+
you (8:05 am) ──▶ ~/.overnight/results/index.md
|
|
16
|
+
✅ tradeoffs of CRDTs vs OT — crdts-vs-ot.md
|
|
17
|
+
✅ litestream vs postgres replication — litestream-vs-postgres.md
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Why
|
|
21
|
+
|
|
22
|
+
- **Limit-aware, not just time-aware.** Cron can run Claude at 3am, but it doesn't know whether your 5-hour window is fresh or your weekly cap is nearly gone. `claude-overnight` checks both before starting and *between every job*, and stops at a configurable threshold so you wake up with quota left for actual work.
|
|
23
|
+
- **Runs on your subscription, unattended.** Jobs execute through `claude -p` (headless mode) with tools restricted to web search — no API key, no extra cost.
|
|
24
|
+
- **Zero dependencies.** Pure Python standard library. `uv tool install claude-overnight` and you're done.
|
|
25
|
+
|
|
26
|
+
## Install
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
uv tool install claude-overnight # or: pipx install claude-overnight
|
|
30
|
+
overnight install # sets up the scheduler + /queue slash command
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
`overnight install` does two things:
|
|
34
|
+
|
|
35
|
+
1. Registers a **launchd agent** that wakes every 30 minutes, checks whether you're inside the night window and under the limit thresholds, and runs the queue if so.
|
|
36
|
+
2. Drops a **`/queue` slash command** into `~/.claude/commands/`, so you can queue questions without leaving Claude Code.
|
|
37
|
+
|
|
38
|
+
Requires macOS, Python 3.11+, and the [Claude Code](https://code.claude.com) CLI with a Pro/Max subscription.
|
|
39
|
+
|
|
40
|
+
## Use
|
|
41
|
+
|
|
42
|
+
```sh
|
|
43
|
+
# from anywhere
|
|
44
|
+
overnight add "how do sqlite WAL checkpoints actually work?"
|
|
45
|
+
|
|
46
|
+
# or inside any Claude Code session
|
|
47
|
+
/queue how do sqlite WAL checkpoints actually work?
|
|
48
|
+
|
|
49
|
+
overnight list # see the queue
|
|
50
|
+
overnight status # current 5h/weekly utilization + would-it-run-now
|
|
51
|
+
overnight run --force # run the batch right now, ignoring window/limits
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Results land in `~/.overnight/results/<date>/`, one markdown report per question, with a rolling `index.md` digest. A macOS notification fires when the batch finishes.
|
|
55
|
+
|
|
56
|
+
## Configure
|
|
57
|
+
|
|
58
|
+
`~/.overnight/config.toml`:
|
|
59
|
+
|
|
60
|
+
```toml
|
|
61
|
+
[window]
|
|
62
|
+
start = "01:00" # jobs only run between these local times
|
|
63
|
+
end = "07:00" # windows may cross midnight ("23:00" → "06:00")
|
|
64
|
+
|
|
65
|
+
[limits]
|
|
66
|
+
start_max_utilization = 20 # don't start if the 5h window is already >20% used
|
|
67
|
+
stop_utilization = 60 # stop the batch once 5h usage crosses 60%
|
|
68
|
+
weekly_max_utilization = 80 # never run if the weekly limit is >80% used
|
|
69
|
+
|
|
70
|
+
[run]
|
|
71
|
+
model = "sonnet"
|
|
72
|
+
job_timeout_minutes = 15
|
|
73
|
+
max_attempts = 2
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## How it works (there's no official API)
|
|
77
|
+
|
|
78
|
+
Claude Code doesn't expose a usage API — but it stores an OAuth token locally (macOS Keychain / `~/.claude/.credentials.json`), and the community discovered that `GET https://api.anthropic.com/api/oauth/usage` with that token returns your 5-hour and weekly utilization with reset timestamps. `claude-overnight` uses that to decide when it's safe to run.
|
|
79
|
+
|
|
80
|
+
Because the endpoint is **undocumented and could change**, everything degrades gracefully: if usage can't be read, the runner proceeds optimistically and detects limit errors from `claude -p` output instead — a job that hits the limit is requeued untouched for the next window, and the batch stops.
|
|
81
|
+
|
|
82
|
+
Full details in [docs/how-it-works.md](docs/how-it-works.md).
|
|
83
|
+
|
|
84
|
+
## Edge cases handled
|
|
85
|
+
|
|
86
|
+
- **Mac asleep at 3am** — launchd runs the missed tick on wake, so the batch runs when you open the lid, still before you start working.
|
|
87
|
+
- **One job eating the whole window** — per-job timeouts plus utilization re-checks between jobs.
|
|
88
|
+
- **Two runners racing** — lockfile with stale-lock recovery.
|
|
89
|
+
- **Headless Claude wanting to ask you something** — the prompt template instructs it to make reasonable assumptions and state them.
|
|
90
|
+
- **Weekly cap already blown** — the runner skips the batch and says why in `overnight status`.
|
|
91
|
+
|
|
92
|
+
## Uninstall
|
|
93
|
+
|
|
94
|
+
```sh
|
|
95
|
+
overnight uninstall # removes the launchd agent + slash command
|
|
96
|
+
uv tool uninstall claude-overnight
|
|
97
|
+
rm -rf ~/.overnight # queue, results, config
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Roadmap
|
|
101
|
+
|
|
102
|
+
- Repo-scoped coding jobs (run in a git worktree, results as branches)
|
|
103
|
+
- Linux (systemd timer) support
|
|
104
|
+
- "Quota saved this week" stats in `overnight status`
|
|
105
|
+
|
|
106
|
+
## License
|
|
107
|
+
|
|
108
|
+
MIT
|
|
Binary file
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Queue a question for claude-overnight to research when your limits reset
|
|
3
|
+
allowed-tools: Bash(overnight add:*), Bash(overnight status:*)
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Queue the user's question for overnight research.
|
|
7
|
+
|
|
8
|
+
1. Run `overnight add "$ARGUMENTS"`.
|
|
9
|
+
2. If the command is not found, tell the user to install the CLI first:
|
|
10
|
+
`uv tool install claude-overnight && overnight install`.
|
|
11
|
+
3. On success, confirm the question was queued and mention it will run in
|
|
12
|
+
the next overnight window (they can check with `overnight status`).
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Configuration
|
|
2
|
+
|
|
3
|
+
Everything lives in `~/.overnight/config.toml`, created with defaults on first run (`overnight install` or any command). Set `OVERNIGHT_HOME` to relocate the whole state directory.
|
|
4
|
+
|
|
5
|
+
```toml
|
|
6
|
+
[window]
|
|
7
|
+
start = "01:00"
|
|
8
|
+
end = "07:00"
|
|
9
|
+
|
|
10
|
+
[limits]
|
|
11
|
+
start_max_utilization = 20
|
|
12
|
+
stop_utilization = 60
|
|
13
|
+
weekly_max_utilization = 80
|
|
14
|
+
|
|
15
|
+
[run]
|
|
16
|
+
model = "sonnet"
|
|
17
|
+
job_timeout_minutes = 15
|
|
18
|
+
max_attempts = 2
|
|
19
|
+
extra_args = []
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## `[window]`
|
|
23
|
+
|
|
24
|
+
| Key | Default | Meaning |
|
|
25
|
+
| --- | --- | --- |
|
|
26
|
+
| `start`, `end` | `01:00` / `07:00` | Local times bounding when batches may run. `start = "23:00", end = "06:00"` crosses midnight and works as expected. |
|
|
27
|
+
|
|
28
|
+
Pick a window that starts *after* your last coding session's 5-hour window has reset, and ends *before* you start work. The runner ticks every 30 minutes inside the window, so a batch blocked at 1:00 (limits still hot) will retry at 1:30, 2:00, …
|
|
29
|
+
|
|
30
|
+
## `[limits]`
|
|
31
|
+
|
|
32
|
+
| Key | Default | Meaning |
|
|
33
|
+
| --- | --- | --- |
|
|
34
|
+
| `start_max_utilization` | `20` | Don't start a batch unless the 5-hour window is at most this % used. |
|
|
35
|
+
| `stop_utilization` | `60` | Stop the batch once 5-hour usage reaches this %. What's left is your morning buffer. |
|
|
36
|
+
| `weekly_max_utilization` | `80` | Never run if weekly usage exceeds this %. Protects your work-week quota. |
|
|
37
|
+
|
|
38
|
+
If usage can't be read at all (expired token, endpoint changed), the runner proceeds and stops on the first limit error instead.
|
|
39
|
+
|
|
40
|
+
## `[run]`
|
|
41
|
+
|
|
42
|
+
| Key | Default | Meaning |
|
|
43
|
+
| --- | --- | --- |
|
|
44
|
+
| `model` | `sonnet` | Passed to `claude --model`. Use `haiku` to stretch quota further, `opus` for depth. |
|
|
45
|
+
| `job_timeout_minutes` | `15` | Hard kill per job. |
|
|
46
|
+
| `max_attempts` | `2` | Failed/limit-hit jobs are retried on later ticks up to this many times. |
|
|
47
|
+
| `extra_args` | `[]` | Extra flags appended to the `claude -p` invocation, e.g. `["--fallback-model", "haiku"]`. |
|
|
48
|
+
|
|
49
|
+
## Paths
|
|
50
|
+
|
|
51
|
+
| Path | Contents |
|
|
52
|
+
| --- | --- |
|
|
53
|
+
| `~/.overnight/queue/` | One JSON file per job |
|
|
54
|
+
| `~/.overnight/results/<date>/` | Markdown reports |
|
|
55
|
+
| `~/.overnight/results/index.md` | Rolling digest, newest batch last |
|
|
56
|
+
| `~/.overnight/logs/` | launchd runner stdout/stderr |
|
|
57
|
+
| `~/Library/LaunchAgents/com.claude-overnight.runner.plist` | The scheduler |
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Design
|
|
2
|
+
|
|
3
|
+
*Spec for claude-overnight v0.1, 2026-07-15.*
|
|
4
|
+
|
|
5
|
+
## Problem
|
|
6
|
+
|
|
7
|
+
Claude Code Pro/Max plans have a rolling 5-hour limit and a weekly limit. Heavy users hit the 5-hour cap daily yet still leave weekly quota unused, because nights are idle. That surplus expires silently.
|
|
8
|
+
|
|
9
|
+
## Goal
|
|
10
|
+
|
|
11
|
+
Let a user queue research questions at any time, and run them unattended overnight — inside a configurable window, only when limits have actually reset, stopping before the fresh window is spent — with results delivered as markdown reports plus a digest.
|
|
12
|
+
|
|
13
|
+
**Non-goals (v0.1):** repo-scoped coding jobs, Linux/Windows support, priorities/dependencies between jobs, any server component.
|
|
14
|
+
|
|
15
|
+
## Architecture
|
|
16
|
+
|
|
17
|
+
Three pieces, one data directory (`~/.overnight`):
|
|
18
|
+
|
|
19
|
+
1. **CLI (`overnight`)** — queue management (`add/list/remove`), visibility (`status`), execution (`run`), setup (`install/uninstall`). Pure-stdlib Python 3.11+.
|
|
20
|
+
2. **`/queue` slash command + Claude Code plugin** — thin wrappers that shell out to `overnight add`, so queueing never requires leaving a session.
|
|
21
|
+
3. **Runner + launchd agent** — a 30-minute tick; each tick is idempotent and cheap when there's nothing to do (no queue, outside window, limits hot, or another runner holds the lock).
|
|
22
|
+
|
|
23
|
+
Module boundaries (`src/overnight/`):
|
|
24
|
+
|
|
25
|
+
| Module | Responsibility | Depends on |
|
|
26
|
+
| --- | --- | --- |
|
|
27
|
+
| `paths` | filesystem layout, `OVERNIGHT_HOME` override | — |
|
|
28
|
+
| `config` | TOML config, window math | `paths` |
|
|
29
|
+
| `store` | job persistence (one JSON per job), status transitions | `paths` |
|
|
30
|
+
| `limits` | OAuth token discovery, usage endpoint, defensive parsing | — |
|
|
31
|
+
| `runner` | start/continue decisions, job execution, results, index, lock | all above |
|
|
32
|
+
| `notify` | best-effort macOS notification | — |
|
|
33
|
+
| `install` | launchd plist, slash command file | `paths` |
|
|
34
|
+
| `cli` | argument parsing only | all above |
|
|
35
|
+
|
|
36
|
+
## Key decisions
|
|
37
|
+
|
|
38
|
+
- **Decisions are pure functions** (`should_start`, `should_continue`) taking config + usage + clock, so the threshold logic is unit-testable without network or subprocesses.
|
|
39
|
+
- **Usage unknown ≠ usage zero.** `fetch_usage()` returns `None` on any failure and callers proceed optimistically, relying on limit-error detection in `claude -p` output. This is what keeps the tool alive if the undocumented endpoint changes.
|
|
40
|
+
- **Limit-hit jobs are requeued, not failed.** A job that ran into the cap did nothing wrong; it runs next window. The batch stops immediately.
|
|
41
|
+
- **One JSON file per job** instead of a single queue file: no read-modify-write races between `add` (interactive) and the runner; the runner's lockfile only guards batch execution.
|
|
42
|
+
- **Jobs run in a scratch cwd with tools restricted to `WebSearch,WebFetch`** — an unattended agent gets no access to the user's projects.
|
|
43
|
+
|
|
44
|
+
## Testing
|
|
45
|
+
|
|
46
|
+
32+ unit tests cover: store roundtrips, config parsing and midnight-crossing windows, both usage payload shapes (modern `limits` list, legacy `five_hour`/`seven_day`), start/continue threshold decisions, job success/failure/timeout/limit-requeue paths, batch behavior (index writing, mid-batch limit stop, lock contention, attempt exhaustion). The `claude` subprocess and the usage endpoint are mocked; `OVERNIGHT_HOME` isolates all filesystem state per test.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# How it works
|
|
2
|
+
|
|
3
|
+
There is no official API for Claude Code subscription limits. This document explains what `claude-overnight` does instead, so you can judge the tradeoffs yourself.
|
|
4
|
+
|
|
5
|
+
## Reading your limits
|
|
6
|
+
|
|
7
|
+
Claude Code authenticates Pro/Max subscriptions with an OAuth token it stores locally:
|
|
8
|
+
|
|
9
|
+
- **macOS Keychain**, service `Claude Code-credentials` (the default on Macs)
|
|
10
|
+
- `~/.claude/.credentials.json` (Linux, and some macOS setups)
|
|
11
|
+
|
|
12
|
+
The community discovered that this token works against an undocumented endpoint:
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
GET https://api.anthropic.com/api/oauth/usage
|
|
16
|
+
Authorization: Bearer <accessToken>
|
|
17
|
+
anthropic-beta: oauth-2025-04-20
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
The response contains a `limits` list with entries like:
|
|
21
|
+
|
|
22
|
+
```json
|
|
23
|
+
{ "kind": "session", "percent": 27.5, "resets_at": "2026-07-15T09:29:59Z" }
|
|
24
|
+
{ "kind": "weekly_scoped", "percent": 44, "resets_at": "2026-07-18T18:59:59Z",
|
|
25
|
+
"scope": { "model": { "display_name": "Fable" } } }
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
- `session` is the rolling **5-hour window**.
|
|
29
|
+
- `weekly_scoped` entries are the **weekly limits**, potentially one per model. `claude-overnight` conservatively uses the highest active one.
|
|
30
|
+
|
|
31
|
+
Older accounts return a legacy shape (`five_hour` / `seven_day` objects with a `utilization` field); the parser handles both.
|
|
32
|
+
|
|
33
|
+
The token is **read at runtime and never stored, logged, or sent anywhere except `api.anthropic.com`** — the same place Claude Code itself sends it.
|
|
34
|
+
|
|
35
|
+
## Deciding when to run
|
|
36
|
+
|
|
37
|
+
A launchd agent ticks every 30 minutes. On each tick the runner checks, in order:
|
|
38
|
+
|
|
39
|
+
1. **Lockfile** — is another batch already running? (Stale locks older than 2 hours are cleared.)
|
|
40
|
+
2. **Night window** — is the local time inside `[window.start, window.end)`? Windows may cross midnight.
|
|
41
|
+
3. **Weekly cap** — is weekly utilization ≤ `weekly_max_utilization`? If your week is nearly spent, running overnight jobs would eat quota you'll want for real work.
|
|
42
|
+
4. **5-hour start cap** — is the 5-hour window ≤ `start_max_utilization`? A fresh window means the batch gets maximum room.
|
|
43
|
+
|
|
44
|
+
Between every job it re-checks, and stops once the 5-hour window crosses `stop_utilization` — the whole point is to use *surplus* quota, not to hand you an empty tank at 9am.
|
|
45
|
+
|
|
46
|
+
## Running jobs
|
|
47
|
+
|
|
48
|
+
Each job runs through Claude Code's headless mode:
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
claude -p "<wrapped prompt>" --output-format json --model sonnet --allowedTools "WebSearch,WebFetch"
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
- The wrapper instructs Claude that it is unattended: make reasonable assumptions, state them, and produce a self-contained markdown report with sources.
|
|
55
|
+
- Tools are restricted to web search/fetch; jobs run in an empty scratch directory, so they can't touch your projects.
|
|
56
|
+
- Each job has a timeout (default 15 minutes) and a max attempt count (default 2).
|
|
57
|
+
|
|
58
|
+
## Failure modes, by design
|
|
59
|
+
|
|
60
|
+
| What breaks | What happens |
|
|
61
|
+
| --- | --- |
|
|
62
|
+
| Usage endpoint changes or disappears | `fetch_usage()` returns `None`; the runner proceeds optimistically and relies on error detection instead |
|
|
63
|
+
| Token expired (Claude Code hasn't run in a while) | Same as above — and `claude -p` refreshes tokens itself when it runs |
|
|
64
|
+
| A job hits the usage limit mid-batch | The job is **requeued untouched**, the batch stops, and the next tick (or next night) retries |
|
|
65
|
+
| Mac asleep during the window | launchd runs the missed tick on wake |
|
|
66
|
+
| `claude` CLI missing | Job marked failed with a clear error |
|
|
67
|
+
| Two ticks overlap | Lockfile prevents the second runner |
|
|
68
|
+
|
|
69
|
+
## What this is not
|
|
70
|
+
|
|
71
|
+
- It does not bypass, extend, or game your limits — it only runs work you queued, on your own subscription, at times your quota would otherwise sit idle.
|
|
72
|
+
- It is not affiliated with Anthropic, and the usage endpoint could change without notice. The tool is built to degrade to "try and detect" rather than break.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "claude-overnight"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Queue research questions all day. Wake up to answers. Puts your idle Claude Code quota to work while you sleep."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.11"
|
|
7
|
+
license = { text = "MIT" }
|
|
8
|
+
authors = [{ name = "Rohan Richard" }]
|
|
9
|
+
keywords = ["claude", "claude-code", "queue", "rate-limits", "automation"]
|
|
10
|
+
classifiers = [
|
|
11
|
+
"Development Status :: 4 - Beta",
|
|
12
|
+
"Environment :: Console",
|
|
13
|
+
"Intended Audience :: Developers",
|
|
14
|
+
"License :: OSI Approved :: MIT License",
|
|
15
|
+
"Operating System :: MacOS",
|
|
16
|
+
"Programming Language :: Python :: 3.11",
|
|
17
|
+
"Programming Language :: Python :: 3.12",
|
|
18
|
+
"Topic :: Software Development :: Build Tools",
|
|
19
|
+
]
|
|
20
|
+
dependencies = []
|
|
21
|
+
|
|
22
|
+
[project.urls]
|
|
23
|
+
Homepage = "https://github.com/rohanprichard/claude-overnight"
|
|
24
|
+
Issues = "https://github.com/rohanprichard/claude-overnight/issues"
|
|
25
|
+
|
|
26
|
+
[project.scripts]
|
|
27
|
+
overnight = "overnight.cli:main"
|
|
28
|
+
|
|
29
|
+
[build-system]
|
|
30
|
+
requires = ["hatchling"]
|
|
31
|
+
build-backend = "hatchling.build"
|
|
32
|
+
|
|
33
|
+
[tool.hatch.build.targets.wheel]
|
|
34
|
+
packages = ["src/overnight"]
|
|
35
|
+
|
|
36
|
+
[dependency-groups]
|
|
37
|
+
dev = ["pytest>=8"]
|