cc-peak-hours 1.0.0
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.
- package/.github/ISSUE_TEMPLATE/bug_report.md +35 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +19 -0
- package/.github/pull_request_template.md +19 -0
- package/.github/workflows/ci.yml +58 -0
- package/CHANGELOG.md +19 -0
- package/CODE_OF_CONDUCT.md +41 -0
- package/CONTRIBUTING.md +9 -0
- package/LICENSE +21 -0
- package/README.md +124 -0
- package/SECURITY.md +33 -0
- package/assets/full-en.svg +7 -0
- package/assets/full-fr.svg +7 -0
- package/assets/gen-svg.sh +94 -0
- package/assets/minimal-offpeak-en.svg +4 -0
- package/assets/minimal-offpeak-fr.svg +4 -0
- package/bin/install.js +159 -0
- package/bin/statusline.sh +557 -0
- package/docs/superpowers/plans/2026-03-31-claude-peak-hours.md +1115 -0
- package/docs/superpowers/specs/2026-03-31-claude-peak-hours-design.md +232 -0
- package/package.json +21 -0
- package/peak-hours.json +6 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
# claude-peak-hours — Design Spec
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
Claude Code statusline plugin that permanently displays whether the user is in **peak** or **off-peak** hours, with countdown to next transition. Inspired by [isclaude-2x](https://github.com/Adiazgallici/isclaude-2x), adapted for permanent use (not tied to a temporary promotion).
|
|
6
|
+
|
|
7
|
+
## Peak Hours Definition
|
|
8
|
+
|
|
9
|
+
- **Peak**: 12:00 – 18:00 UTC, weekdays only (= 8AM–2PM ET / 14h–20h CET)
|
|
10
|
+
- **Off-peak**: all other times (evenings, nights, weekends)
|
|
11
|
+
- Defined in UTC to avoid DST ambiguity — converted to local time for display
|
|
12
|
+
|
|
13
|
+
## Project Structure
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
claude-peak-hours/
|
|
17
|
+
├── bin/
|
|
18
|
+
│ ├── statusline.sh # Main statusline script (bash)
|
|
19
|
+
│ └── install.js # Node installer with backup/uninstall
|
|
20
|
+
├── peak-hours.json # Remote config (peak hours definition)
|
|
21
|
+
├── package.json # npm metadata (npx claude-peak-hours)
|
|
22
|
+
├── LICENSE # MIT
|
|
23
|
+
└── README.md
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npx claude-peak-hours # minimal mode, auto time format & language
|
|
30
|
+
npx claude-peak-hours --full # dashboard mode
|
|
31
|
+
npx claude-peak-hours --24h # force 24h format
|
|
32
|
+
npx claude-peak-hours --lang fr # force French
|
|
33
|
+
npx claude-peak-hours --full --24h --lang fr # combined
|
|
34
|
+
npx claude-peak-hours --uninstall # restore previous statusline
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Installer Behavior (install.js)
|
|
38
|
+
|
|
39
|
+
1. Check dependencies (`jq`, `curl`)
|
|
40
|
+
2. Backup existing statusline to `~/.claude/statusline.sh.bak`
|
|
41
|
+
3. Copy `statusline.sh` to `~/.claude/statusline.sh`
|
|
42
|
+
4. Update `~/.claude/settings.json` with `statusLine` config
|
|
43
|
+
5. Print summary with chosen mode
|
|
44
|
+
|
|
45
|
+
## Display Modes
|
|
46
|
+
|
|
47
|
+
### Minimal (default) — single line
|
|
48
|
+
|
|
49
|
+
**During peak (weekday 8AM-2PM ET):**
|
|
50
|
+
```
|
|
51
|
+
Opus 4.6 │ 39% │ 🔴 Peak ~ Off-peak 20:00 (2h15m)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**During off-peak:**
|
|
55
|
+
```
|
|
56
|
+
Opus 4.6 │ 39% │ ⚡ Off-peak ~ Peak 14:00 (5h12m)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**Weekend:**
|
|
60
|
+
```
|
|
61
|
+
Opus 4.6 │ 39% │ ⚡ Off-peak ~ Peak lun. 14:00 (1d8h)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Full (`--full`) — multi-line dashboard
|
|
65
|
+
|
|
66
|
+
Adds below the minimal line:
|
|
67
|
+
|
|
68
|
+
- **Timeline bar**: 48 chars (2 per hour), green = off-peak, yellow = peak, `●` = now
|
|
69
|
+
- **Rate limits**: session 5h (% + reset time) and weekly (% + reset date) via OAuth API
|
|
70
|
+
|
|
71
|
+
**Example off-peak weekday:**
|
|
72
|
+
```
|
|
73
|
+
Opus 4.6 │ 39% │ ⚡ Off-peak ~ Peak 14:00 (5h12m)
|
|
74
|
+
|
|
75
|
+
today ━━━━━━━━━━━━━━━━━━━━━━━━━━━━●━━━━━━━━━━━━━━━━━━━━ ━ off-peak ━ peak 14:00-20:00 ● now
|
|
76
|
+
current ●●●●○○○○○○ 42% ⟳ 22:00 │ weekly ●●●○○○○○○○ 31% ⟳ mar 20
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**Example weekend:**
|
|
80
|
+
```
|
|
81
|
+
Opus 4.6 │ 12% │ ⚡ Off-peak ~ Peak lun. 14:00 (1d14h)
|
|
82
|
+
|
|
83
|
+
today ━━━━━━━━━━━━━━━━━━━━━━━━━━━━●━━━━━━━━━━━━━━━━━━━━ ━ off-peak all day ● now
|
|
84
|
+
current ●○○○○○○○○○ 5% ⟳ 20:00 │ weekly ●●○○○○○○○○ 15% ⟳ mar 22
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Time Format
|
|
88
|
+
|
|
89
|
+
- `--24h` flag: force 24h format (`14:00`, `20:00`)
|
|
90
|
+
- `--12h` flag: force 12h format (`2:00pm`, `8:00pm`)
|
|
91
|
+
- No flag: auto-detect from locale:
|
|
92
|
+
- `fr_*`, `de_*`, `es_*`, `pt_*`, `it_*`, `ja_*`, `zh_*`, `ko_*`, `ru_*` → 24h
|
|
93
|
+
- `en_US`, `en_*` → 12h
|
|
94
|
+
- Fallback → 12h
|
|
95
|
+
|
|
96
|
+
## Remote Config (peak-hours.json)
|
|
97
|
+
|
|
98
|
+
Peak hours are defined in a JSON file hosted on the project's GitHub repo. This allows updating peak hours for all users without requiring a plugin update.
|
|
99
|
+
|
|
100
|
+
### File format
|
|
101
|
+
|
|
102
|
+
```json
|
|
103
|
+
{
|
|
104
|
+
"version": 2,
|
|
105
|
+
"peak_windows": [
|
|
106
|
+
{ "days": [1, 2, 3, 4, 5], "start_utc": 12, "end_utc": 18 }
|
|
107
|
+
]
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
- All times are in **UTC** — no timezone field needed, no DST ambiguity
|
|
112
|
+
- `peak_windows`: array of peak windows, each with:
|
|
113
|
+
- `days`: ISO weekdays (1=Monday, 7=Sunday) — evaluated in UTC
|
|
114
|
+
- `start_utc` / `end_utc`: peak window boundaries (24h integers, UTC)
|
|
115
|
+
- `version`: schema version for future-proofing
|
|
116
|
+
- The script converts UTC boundaries to local time for display
|
|
117
|
+
|
|
118
|
+
Current peak hours (8AM-2PM ET) = **12:00-18:00 UTC**.
|
|
119
|
+
|
|
120
|
+
This format supports multiple peak windows:
|
|
121
|
+
|
|
122
|
+
```json
|
|
123
|
+
{
|
|
124
|
+
"version": 2,
|
|
125
|
+
"peak_windows": [
|
|
126
|
+
{ "days": [1, 2, 3, 4, 5], "start_utc": 12, "end_utc": 18 },
|
|
127
|
+
{ "days": [1, 2, 3, 4, 5], "start_utc": 22, "end_utc": 2 }
|
|
128
|
+
]
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Note: `end_utc` < `start_utc` means the window crosses midnight UTC.
|
|
133
|
+
|
|
134
|
+
### Fetch & cache strategy
|
|
135
|
+
|
|
136
|
+
- **URL**: raw GitHub URL of `peak-hours.json` from the repo's `main` branch
|
|
137
|
+
- **Cache**: `/tmp/claude/peak-hours-config.json`, TTL = **1 hour**
|
|
138
|
+
- **Fallback**: if fetch fails (no network, GitHub down, timeout), use cached file; if no cache exists, use hardcoded defaults matching the current config above
|
|
139
|
+
- **Timeout**: `curl --max-time 3` to avoid slowing down the statusline
|
|
140
|
+
- The fetch happens at the same time as stdin parsing — no extra latency in the common (cached) case
|
|
141
|
+
|
|
142
|
+
## Script Logic (statusline.sh)
|
|
143
|
+
|
|
144
|
+
### Main Flow
|
|
145
|
+
|
|
146
|
+
1. **Read stdin** — parse JSON (model, context window, usage tokens)
|
|
147
|
+
1. **Load peak hours config** — from cache or remote, fallback to hardcoded defaults
|
|
148
|
+
2. **Calculate context %** — color by threshold:
|
|
149
|
+
- Green: < 50%
|
|
150
|
+
- Orange: 50-70%
|
|
151
|
+
- Yellow: 70-90%
|
|
152
|
+
- Red: > 90%
|
|
153
|
+
3. **Determine peak/off-peak status** (using loaded config):
|
|
154
|
+
- Get current hour and weekday in **UTC** (`TZ=UTC`)
|
|
155
|
+
- Iterate over `peak_windows[]`: check if current UTC day is in `days` AND current UTC hour is within `start_utc`–`end_utc` (handling midnight crossing)
|
|
156
|
+
- If any window matches → peak; otherwise → off-peak
|
|
157
|
+
4. **Calculate countdown** to next transition:
|
|
158
|
+
- If peak → seconds until `end_utc` of the current matching window
|
|
159
|
+
- If off-peak → find the nearest upcoming window start (today or next matching day in UTC), calculate seconds until it
|
|
160
|
+
5. **Convert to local time** for display (all user-facing times are local)
|
|
161
|
+
5. **Convert to local time** for display
|
|
162
|
+
6. **Apply time format** (12h/24h per flag or locale detection)
|
|
163
|
+
|
|
164
|
+
### Full Mode Additions
|
|
165
|
+
|
|
166
|
+
7. **Timeline bar** — 48 chars, 2 per hour, colored by peak/off-peak, dot for current position
|
|
167
|
+
8. **OAuth rate limits fetch** (cached 60s in `/tmp/claude/`):
|
|
168
|
+
- Token from `$CLAUDE_CODE_OAUTH_TOKEN`, macOS Keychain, or `~/.claude/.credentials.json`
|
|
169
|
+
- Endpoint: `https://api.anthropic.com/api/oauth/usage`
|
|
170
|
+
- Display: session 5h (% + reset time) and weekly (% + reset date)
|
|
171
|
+
|
|
172
|
+
### Colors
|
|
173
|
+
|
|
174
|
+
- **Off-peak**: green `⚡` + green text
|
|
175
|
+
- **Peak**: red `🔴` + red/yellow text
|
|
176
|
+
- **Separator**: dim `│`
|
|
177
|
+
- **Context %**: green/orange/yellow/red gradient
|
|
178
|
+
|
|
179
|
+
## Dependencies
|
|
180
|
+
|
|
181
|
+
- `jq` — JSON parsing (stdin + OAuth response)
|
|
182
|
+
- `curl` — rate limits API fetch
|
|
183
|
+
- `date` — GNU coreutils (Linux) or BSD date (macOS)
|
|
184
|
+
- Auto-detection via `date --version`
|
|
185
|
+
|
|
186
|
+
## Compatibility
|
|
187
|
+
|
|
188
|
+
- Linux (GNU coreutils)
|
|
189
|
+
- macOS (BSD date)
|
|
190
|
+
|
|
191
|
+
## Localization (i18n)
|
|
192
|
+
|
|
193
|
+
Two languages supported: English (default) and French.
|
|
194
|
+
|
|
195
|
+
### Detection
|
|
196
|
+
|
|
197
|
+
- `--lang fr` flag: force French
|
|
198
|
+
- `--lang en` flag: force English
|
|
199
|
+
- No flag: auto-detect from `LANG`/`LC_TIME`:
|
|
200
|
+
- `fr_*` → French
|
|
201
|
+
- Everything else → English
|
|
202
|
+
|
|
203
|
+
### Translated strings
|
|
204
|
+
|
|
205
|
+
| Key | English | French |
|
|
206
|
+
|-----|---------|--------|
|
|
207
|
+
| Peak label | `Peak` | `Pointe` |
|
|
208
|
+
| Off-peak label | `Off-peak` | `Hors pointe` |
|
|
209
|
+
| "all day" | `all day` | `toute la journée` |
|
|
210
|
+
| "today" | `today` | `auj.` |
|
|
211
|
+
| "now" | `now` | `maint.` |
|
|
212
|
+
| "current" | `current` | `session` |
|
|
213
|
+
| "weekly" | `weekly` | `hebdo` |
|
|
214
|
+
| Weekday abbrevs | `Mon.` `Tue.` ... | `lun.` `mar.` ... |
|
|
215
|
+
| Month abbrevs | `jan` `feb` ... | `jan` `fév` ... |
|
|
216
|
+
|
|
217
|
+
### Implementation
|
|
218
|
+
|
|
219
|
+
Strings defined as bash variables at the top of `statusline.sh`, set once based on the detected/forced language. No external translation files.
|
|
220
|
+
|
|
221
|
+
## Differences from isclaude-2x
|
|
222
|
+
|
|
223
|
+
| Aspect | isclaude-2x | claude-peak-hours |
|
|
224
|
+
|--------|-------------|-------------------|
|
|
225
|
+
| Purpose | Temporary 2x promo (Mar 13-28) | Permanent peak hours indicator |
|
|
226
|
+
| Labels | "2x" / "1x" | "Off-peak" / "Peak" |
|
|
227
|
+
| Promo countdown | Days remaining in promo | None |
|
|
228
|
+
| Snowflake `❄` | Weekly frozen during 2x | Removed |
|
|
229
|
+
| Time format | 12h only | 12h/24h with auto-detect |
|
|
230
|
+
| Language | English only | English + French with auto-detect |
|
|
231
|
+
| Config | Hardcoded | Remote JSON on GitHub, cached 1h, hardcoded fallback |
|
|
232
|
+
| Lifespan | Expires after promo | Permanent |
|
package/package.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "cc-peak-hours",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Claude Code statusline showing peak/off-peak hours with countdown",
|
|
5
|
+
"bin": {
|
|
6
|
+
"cc-peak-hours": "bin/install.js"
|
|
7
|
+
},
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/nickywan/claude-peak-hours.git"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"claude",
|
|
14
|
+
"claude-code",
|
|
15
|
+
"statusline",
|
|
16
|
+
"peak-hours",
|
|
17
|
+
"cli"
|
|
18
|
+
],
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"author": "nickywan"
|
|
21
|
+
}
|