claude-code-remote-pilot 0.1.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/LICENSE +21 -0
- package/README.md +358 -0
- package/bin/claude-pilot.js +43 -0
- package/claude-pilot.sh +222 -0
- package/package.json +33 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 mekku
|
|
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.
|
package/README.md
ADDED
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
# Claude Code Remote Pilot
|
|
2
|
+
|
|
3
|
+
Interactive Claude Code supervisor using `tmux`, Node.js, and notifications.
|
|
4
|
+
|
|
5
|
+
The current MVP keeps Claude Code usable in the normal terminal while adding a small supervisor layer that can detect usage limits, notify you, wait, and send `continue` automatically.
|
|
6
|
+
|
|
7
|
+
Longer term, this project is moving toward a local multi-session Claude Code dashboard.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Current Status
|
|
12
|
+
|
|
13
|
+
This repository currently contains:
|
|
14
|
+
|
|
15
|
+
- `claude-pilot.sh` — bash/tmux watcher MVP
|
|
16
|
+
- `bin/claude-pilot.js` — npm CLI wrapper
|
|
17
|
+
- `package.json` — npm package entry
|
|
18
|
+
- `docs/ARCHITECTURE_UPDATE.md` — architecture direction
|
|
19
|
+
- `TASKS.md` — implementation roadmap
|
|
20
|
+
|
|
21
|
+
Current mode:
|
|
22
|
+
|
|
23
|
+
```text
|
|
24
|
+
Human → tmux terminal → Claude Code
|
|
25
|
+
↑
|
|
26
|
+
│
|
|
27
|
+
Claude Code Remote Pilot
|
|
28
|
+
- watches output
|
|
29
|
+
- detects limits
|
|
30
|
+
- sends notifications
|
|
31
|
+
- resumes automatically
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Future mode:
|
|
35
|
+
|
|
36
|
+
```text
|
|
37
|
+
Browser Dashboard
|
|
38
|
+
↓
|
|
39
|
+
WebSocket / REST API
|
|
40
|
+
↓
|
|
41
|
+
Node.js Supervisor
|
|
42
|
+
↓
|
|
43
|
+
Session Manager
|
|
44
|
+
↓
|
|
45
|
+
tmux sessions
|
|
46
|
+
↓
|
|
47
|
+
Claude Code instances
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Why This Exists
|
|
53
|
+
|
|
54
|
+
Claude Code is useful for long-running coding tasks, but usage/rate limits can interrupt the flow.
|
|
55
|
+
|
|
56
|
+
This project aims to provide:
|
|
57
|
+
|
|
58
|
+
- persistent Claude Code sessions
|
|
59
|
+
- automatic resume after limit reset
|
|
60
|
+
- human-supervised automation
|
|
61
|
+
- local-first workflow
|
|
62
|
+
- multi-session management
|
|
63
|
+
- eventual web dashboard control
|
|
64
|
+
|
|
65
|
+
It is intentionally **not** designed as an unsafe fully autonomous agent loop.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Features
|
|
70
|
+
|
|
71
|
+
Current MVP:
|
|
72
|
+
|
|
73
|
+
- tmux session persistence
|
|
74
|
+
- auto-create Claude tmux session
|
|
75
|
+
- terminal output capture
|
|
76
|
+
- usage/rate limit detection
|
|
77
|
+
- reset-time parsing
|
|
78
|
+
- Telegram notification support
|
|
79
|
+
- automatic `continue` after waiting
|
|
80
|
+
- duplicate event protection
|
|
81
|
+
- resume cooldown protection
|
|
82
|
+
- npm CLI wrapper
|
|
83
|
+
|
|
84
|
+
Planned:
|
|
85
|
+
|
|
86
|
+
- Node.js runtime refactor
|
|
87
|
+
- multiple Claude sessions
|
|
88
|
+
- web dashboard
|
|
89
|
+
- WebSocket live output
|
|
90
|
+
- session registry
|
|
91
|
+
- pluggable detectors
|
|
92
|
+
- notification providers
|
|
93
|
+
- persistent session state
|
|
94
|
+
- policy/safety engine
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Requirements
|
|
99
|
+
|
|
100
|
+
- Node.js >= 18
|
|
101
|
+
- bash
|
|
102
|
+
- tmux
|
|
103
|
+
- curl
|
|
104
|
+
- python3 recommended for better reset-time parsing
|
|
105
|
+
- Claude Code CLI installed and authenticated
|
|
106
|
+
|
|
107
|
+
macOS:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
brew install tmux
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Ubuntu/Debian:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
sudo apt install tmux curl python3
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Install
|
|
122
|
+
|
|
123
|
+
### From GitHub
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
git clone https://github.com/mekku/claude-code-remote-pilot.git
|
|
127
|
+
cd claude-code-remote-pilot
|
|
128
|
+
yarn install
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Run locally:
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
yarn pilot
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
or:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
node bin/claude-pilot.js
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### As npm package
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
npm install -g claude-code-remote-pilot
|
|
147
|
+
claude-remote-pilot
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
or:
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
npx claude-code-remote-pilot
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Telegram Setup
|
|
159
|
+
|
|
160
|
+
Create a bot with `@BotFather` and get your bot token.
|
|
161
|
+
|
|
162
|
+
Send a message to your bot, then get your chat ID:
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
curl "https://api.telegram.org/bot<BOT_TOKEN>/getUpdates"
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Look for:
|
|
169
|
+
|
|
170
|
+
```json
|
|
171
|
+
"chat": {
|
|
172
|
+
"id": 123456789
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Run:
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
export TELEGRAM_BOT_TOKEN="xxx"
|
|
180
|
+
export TELEGRAM_CHAT_ID="123456789"
|
|
181
|
+
|
|
182
|
+
yarn pilot
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Telegram is optional. If not configured, messages are printed locally.
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Basic Usage
|
|
190
|
+
|
|
191
|
+
Start pilot:
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
yarn pilot
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Attach to Claude:
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
tmux attach -t claude
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Detach without killing Claude:
|
|
204
|
+
|
|
205
|
+
```text
|
|
206
|
+
Ctrl+B then D
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
The watcher keeps running and watches the tmux session.
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## Environment Variables
|
|
214
|
+
|
|
215
|
+
| Variable | Default | Description |
|
|
216
|
+
|---|---:|---|
|
|
217
|
+
| `TELEGRAM_BOT_TOKEN` | empty | Telegram bot token |
|
|
218
|
+
| `TELEGRAM_CHAT_ID` | empty | Telegram chat ID |
|
|
219
|
+
| `CLAUDE_SESSION` | `claude` | tmux session name |
|
|
220
|
+
| `CLAUDE_COMMAND` | `claude` | command used to start Claude |
|
|
221
|
+
| `CHECK_INTERVAL_SECONDS` | `30` | watcher interval |
|
|
222
|
+
| `LIMIT_FALLBACK_WAIT_SECONDS` | `300` | fallback wait if reset time cannot be parsed |
|
|
223
|
+
| `POST_RESUME_COOLDOWN_SECONDS` | `180` | avoid repeated resume spam |
|
|
224
|
+
| `CAPTURE_LINES` | `500` | number of tmux output lines to inspect |
|
|
225
|
+
| `RESUME_COMMAND` | `continue` | command sent after reset |
|
|
226
|
+
| `START_IF_MISSING` | `1` | auto-create tmux session if missing |
|
|
227
|
+
| `LIMIT_REGEX` | built-in | custom regex for limit detection |
|
|
228
|
+
| `PERMISSION_REGEX` | built-in | custom regex for permission detection |
|
|
229
|
+
|
|
230
|
+
Example:
|
|
231
|
+
|
|
232
|
+
```bash
|
|
233
|
+
CLAUDE_SESSION=claude-buildx-api \
|
|
234
|
+
CLAUDE_COMMAND="claude" \
|
|
235
|
+
TELEGRAM_BOT_TOKEN="xxx" \
|
|
236
|
+
TELEGRAM_CHAT_ID="123456789" \
|
|
237
|
+
yarn pilot
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## Recommended Claude Workflow
|
|
243
|
+
|
|
244
|
+
For long-running work, ask Claude to maintain external state:
|
|
245
|
+
|
|
246
|
+
```text
|
|
247
|
+
Maintain TASK_STATE.md.
|
|
248
|
+
After every meaningful step:
|
|
249
|
+
- update completed work
|
|
250
|
+
- update current status
|
|
251
|
+
- update next exact action
|
|
252
|
+
|
|
253
|
+
If interrupted:
|
|
254
|
+
- read TASK_STATE.md
|
|
255
|
+
- resume from the next unfinished step.
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
This matters because LLM context can drift over long sessions. External state is the real resume brain.
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## Safety Notes
|
|
263
|
+
|
|
264
|
+
Avoid starting with:
|
|
265
|
+
|
|
266
|
+
```bash
|
|
267
|
+
claude --dangerously-skip-permissions
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
That mode allows Claude to execute commands and modify files without asking.
|
|
271
|
+
|
|
272
|
+
Claude Code Pilot should begin as a **human-supervised** tool:
|
|
273
|
+
|
|
274
|
+
Allowed early:
|
|
275
|
+
|
|
276
|
+
- watch output
|
|
277
|
+
- notify
|
|
278
|
+
- send `continue`
|
|
279
|
+
- send user-provided input
|
|
280
|
+
- show latest results
|
|
281
|
+
|
|
282
|
+
Avoid early:
|
|
283
|
+
|
|
284
|
+
- auto-approve permissions
|
|
285
|
+
- arbitrary remote shell execution
|
|
286
|
+
- autonomous destructive actions
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
## Roadmap
|
|
291
|
+
|
|
292
|
+
See:
|
|
293
|
+
|
|
294
|
+
- [`docs/ARCHITECTURE_UPDATE.md`](docs/ARCHITECTURE_UPDATE.md)
|
|
295
|
+
- [`TASKS.md`](TASKS.md)
|
|
296
|
+
|
|
297
|
+
High-level phases:
|
|
298
|
+
|
|
299
|
+
1. tmux watcher MVP
|
|
300
|
+
2. Node.js runtime refactor
|
|
301
|
+
3. multi-session support
|
|
302
|
+
4. detection engine
|
|
303
|
+
5. notification providers
|
|
304
|
+
6. web dashboard
|
|
305
|
+
7. persistent state
|
|
306
|
+
8. safety/policy engine
|
|
307
|
+
9. optional node-pty backend
|
|
308
|
+
|
|
309
|
+
---
|
|
310
|
+
|
|
311
|
+
## Target Dashboard Concept
|
|
312
|
+
|
|
313
|
+
Planned local dashboard:
|
|
314
|
+
|
|
315
|
+
```text
|
|
316
|
+
Sidebar
|
|
317
|
+
├── claude-buildx-api
|
|
318
|
+
├── claude-pos-mobile
|
|
319
|
+
├── claude-auth-refactor
|
|
320
|
+
└── claude-research
|
|
321
|
+
|
|
322
|
+
Main Panel
|
|
323
|
+
├── live output
|
|
324
|
+
├── status badge
|
|
325
|
+
├── input box
|
|
326
|
+
├── continue button
|
|
327
|
+
└── auto-resume toggle
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
This allows normal local terminal interaction when at the machine, plus remote-lite control when away.
|
|
331
|
+
|
|
332
|
+
---
|
|
333
|
+
|
|
334
|
+
## Philosophy
|
|
335
|
+
|
|
336
|
+
Claude Code Pilot is intended to become:
|
|
337
|
+
|
|
338
|
+
```text
|
|
339
|
+
human-supervised local AI runtime
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
not:
|
|
343
|
+
|
|
344
|
+
```text
|
|
345
|
+
fully autonomous AI operator
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
The system prioritizes:
|
|
349
|
+
|
|
350
|
+
- persistence
|
|
351
|
+
- observability
|
|
352
|
+
- recoverability
|
|
353
|
+
- resumability
|
|
354
|
+
- multi-session control
|
|
355
|
+
- human intervention
|
|
356
|
+
- practical workflows
|
|
357
|
+
|
|
358
|
+
Small tools first. Spaceship later.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { spawn } = require('child_process');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
|
|
7
|
+
const scriptPath = path.resolve(__dirname, '..', 'claude-pilot.sh');
|
|
8
|
+
|
|
9
|
+
if (!fs.existsSync(scriptPath)) {
|
|
10
|
+
console.error('claude-pilot.sh not found');
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (process.argv.includes('--help') || process.argv.includes('-h')) {
|
|
15
|
+
console.log(`
|
|
16
|
+
Claude Code Remote Pilot
|
|
17
|
+
|
|
18
|
+
Usage:
|
|
19
|
+
claude-remote-pilot
|
|
20
|
+
|
|
21
|
+
Environment variables:
|
|
22
|
+
TELEGRAM_BOT_TOKEN
|
|
23
|
+
TELEGRAM_CHAT_ID
|
|
24
|
+
CLAUDE_SESSION
|
|
25
|
+
CLAUDE_COMMAND
|
|
26
|
+
|
|
27
|
+
Example:
|
|
28
|
+
TELEGRAM_BOT_TOKEN=xxx TELEGRAM_CHAT_ID=123456 claude-remote-pilot
|
|
29
|
+
|
|
30
|
+
Attach to tmux:
|
|
31
|
+
tmux attach -t claude
|
|
32
|
+
`);
|
|
33
|
+
process.exit(0);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const child = spawn('bash', [scriptPath], {
|
|
37
|
+
stdio: 'inherit',
|
|
38
|
+
env: process.env,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
child.on('exit', (code) => {
|
|
42
|
+
process.exit(code || 0);
|
|
43
|
+
});
|
package/claude-pilot.sh
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -u
|
|
3
|
+
|
|
4
|
+
# Claude Code Pilot
|
|
5
|
+
# Interactive Claude Code supervisor using tmux + Telegram notifications.
|
|
6
|
+
#
|
|
7
|
+
# Required env:
|
|
8
|
+
# TELEGRAM_BOT_TOKEN
|
|
9
|
+
# TELEGRAM_CHAT_ID
|
|
10
|
+
#
|
|
11
|
+
# Optional env:
|
|
12
|
+
# CLAUDE_SESSION=claude
|
|
13
|
+
# CLAUDE_COMMAND=claude
|
|
14
|
+
# CHECK_INTERVAL_SECONDS=30
|
|
15
|
+
# LIMIT_FALLBACK_WAIT_SECONDS=300
|
|
16
|
+
# POST_RESUME_COOLDOWN_SECONDS=180
|
|
17
|
+
# CAPTURE_LINES=500
|
|
18
|
+
# RESUME_COMMAND=continue
|
|
19
|
+
# START_IF_MISSING=1
|
|
20
|
+
#
|
|
21
|
+
# Example:
|
|
22
|
+
# TELEGRAM_BOT_TOKEN="123:abc" TELEGRAM_CHAT_ID="123456" ./claude-pilot.sh
|
|
23
|
+
|
|
24
|
+
CLAUDE_SESSION="${CLAUDE_SESSION:-claude}"
|
|
25
|
+
CLAUDE_COMMAND="${CLAUDE_COMMAND:-claude}"
|
|
26
|
+
CHECK_INTERVAL_SECONDS="${CHECK_INTERVAL_SECONDS:-30}"
|
|
27
|
+
LIMIT_FALLBACK_WAIT_SECONDS="${LIMIT_FALLBACK_WAIT_SECONDS:-300}"
|
|
28
|
+
POST_RESUME_COOLDOWN_SECONDS="${POST_RESUME_COOLDOWN_SECONDS:-180}"
|
|
29
|
+
CAPTURE_LINES="${CAPTURE_LINES:-500}"
|
|
30
|
+
RESUME_COMMAND="${RESUME_COMMAND:-continue}"
|
|
31
|
+
START_IF_MISSING="${START_IF_MISSING:-1}"
|
|
32
|
+
|
|
33
|
+
TELEGRAM_BOT_TOKEN="${TELEGRAM_BOT_TOKEN:-}"
|
|
34
|
+
TELEGRAM_CHAT_ID="${TELEGRAM_CHAT_ID:-}"
|
|
35
|
+
|
|
36
|
+
LIMIT_REGEX="${LIMIT_REGEX:-hit your limit|usage limit|rate limit|limit reached|try again|resets}"
|
|
37
|
+
PERMISSION_REGEX="${PERMISSION_REGEX:-permission|do you want to proceed|approve}"
|
|
38
|
+
|
|
39
|
+
last_limit_hash=""
|
|
40
|
+
last_resume_epoch=0
|
|
41
|
+
|
|
42
|
+
log() {
|
|
43
|
+
printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*"
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
require_command() {
|
|
47
|
+
if ! command -v "$1" >/dev/null 2>&1; then
|
|
48
|
+
echo "Missing required command: $1" >&2
|
|
49
|
+
exit 1
|
|
50
|
+
fi
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
send_telegram() {
|
|
54
|
+
local message="$1"
|
|
55
|
+
|
|
56
|
+
if [ -z "$TELEGRAM_BOT_TOKEN" ] || [ -z "$TELEGRAM_CHAT_ID" ]; then
|
|
57
|
+
log "Telegram not configured: $message"
|
|
58
|
+
return 0
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
curl -sS -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
|
|
62
|
+
--data-urlencode "chat_id=${TELEGRAM_CHAT_ID}" \
|
|
63
|
+
--data-urlencode "text=${message}" >/dev/null || true
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
capture_pane() {
|
|
67
|
+
tmux capture-pane -pt "$CLAUDE_SESSION" -S "-${CAPTURE_LINES}" 2>/dev/null || true
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
hash_text() {
|
|
71
|
+
if command -v sha256sum >/dev/null 2>&1; then
|
|
72
|
+
sha256sum | awk '{print $1}'
|
|
73
|
+
else
|
|
74
|
+
shasum -a 256 | awk '{print $1}'
|
|
75
|
+
fi
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
parse_wait_seconds() {
|
|
79
|
+
local text="$1"
|
|
80
|
+
|
|
81
|
+
if command -v python3 >/dev/null 2>&1; then
|
|
82
|
+
TEXT="$text" FALLBACK="$LIMIT_FALLBACK_WAIT_SECONDS" python3 - <<'PY'
|
|
83
|
+
import os
|
|
84
|
+
import re
|
|
85
|
+
from datetime import datetime, timedelta
|
|
86
|
+
|
|
87
|
+
text = os.environ.get("TEXT", "")
|
|
88
|
+
fallback = int(os.environ.get("FALLBACK", "300"))
|
|
89
|
+
now = datetime.now()
|
|
90
|
+
lower = text.lower()
|
|
91
|
+
|
|
92
|
+
# Examples:
|
|
93
|
+
# try again in 12 minutes
|
|
94
|
+
# retry after 1 hour
|
|
95
|
+
m = re.search(r"(?:try again|retry|wait).*?in\s+(\d+)\s*(second|seconds|minute|minutes|hour|hours)", lower, re.I | re.S)
|
|
96
|
+
if m:
|
|
97
|
+
value = int(m.group(1))
|
|
98
|
+
unit = m.group(2)
|
|
99
|
+
if unit.startswith("second"):
|
|
100
|
+
print(max(10, value))
|
|
101
|
+
elif unit.startswith("minute"):
|
|
102
|
+
print(max(10, value * 60))
|
|
103
|
+
else:
|
|
104
|
+
print(max(10, value * 3600))
|
|
105
|
+
raise SystemExit
|
|
106
|
+
|
|
107
|
+
# Examples:
|
|
108
|
+
# resets 2am
|
|
109
|
+
# resets at 14:30
|
|
110
|
+
# limit resets at 3:05 pm
|
|
111
|
+
m = re.search(r"resets?\s+(?:at\s+)?(\d{1,2})(?::(\d{2}))?\s*(am|pm)?", lower, re.I)
|
|
112
|
+
if m:
|
|
113
|
+
hour = int(m.group(1))
|
|
114
|
+
minute = int(m.group(2) or 0)
|
|
115
|
+
ampm = m.group(3)
|
|
116
|
+
|
|
117
|
+
if ampm:
|
|
118
|
+
if ampm == "pm" and hour != 12:
|
|
119
|
+
hour += 12
|
|
120
|
+
if ampm == "am" and hour == 12:
|
|
121
|
+
hour = 0
|
|
122
|
+
|
|
123
|
+
if 0 <= hour <= 23 and 0 <= minute <= 59:
|
|
124
|
+
reset = now.replace(hour=hour, minute=minute, second=0, microsecond=0)
|
|
125
|
+
if reset <= now:
|
|
126
|
+
reset += timedelta(days=1)
|
|
127
|
+
# Add small safety buffer so we do not resume one second too early.
|
|
128
|
+
print(max(10, int((reset - now).total_seconds()) + 30))
|
|
129
|
+
raise SystemExit
|
|
130
|
+
|
|
131
|
+
print(fallback)
|
|
132
|
+
PY
|
|
133
|
+
return 0
|
|
134
|
+
fi
|
|
135
|
+
|
|
136
|
+
echo "$LIMIT_FALLBACK_WAIT_SECONDS"
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
ensure_tmux_session() {
|
|
140
|
+
if tmux has-session -t "$CLAUDE_SESSION" 2>/dev/null; then
|
|
141
|
+
return 0
|
|
142
|
+
fi
|
|
143
|
+
|
|
144
|
+
if [ "$START_IF_MISSING" != "1" ]; then
|
|
145
|
+
echo "tmux session '$CLAUDE_SESSION' not found." >&2
|
|
146
|
+
exit 1
|
|
147
|
+
fi
|
|
148
|
+
|
|
149
|
+
tmux new-session -d -s "$CLAUDE_SESSION" "$CLAUDE_COMMAND"
|
|
150
|
+
log "Started tmux session '$CLAUDE_SESSION' with command: $CLAUDE_COMMAND"
|
|
151
|
+
send_telegram "Claude Pilot: started tmux session '$CLAUDE_SESSION'."
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
within_resume_cooldown() {
|
|
155
|
+
local now
|
|
156
|
+
now=$(date +%s)
|
|
157
|
+
[ $((now - last_resume_epoch)) -lt "$POST_RESUME_COOLDOWN_SECONDS" ]
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
handle_limit() {
|
|
161
|
+
local text="$1"
|
|
162
|
+
local fingerprint
|
|
163
|
+
fingerprint=$(printf '%s' "$text" | tail -n 40 | hash_text)
|
|
164
|
+
|
|
165
|
+
if [ "$fingerprint" = "$last_limit_hash" ]; then
|
|
166
|
+
return 0
|
|
167
|
+
fi
|
|
168
|
+
|
|
169
|
+
if within_resume_cooldown; then
|
|
170
|
+
return 0
|
|
171
|
+
fi
|
|
172
|
+
|
|
173
|
+
last_limit_hash="$fingerprint"
|
|
174
|
+
|
|
175
|
+
local wait_seconds
|
|
176
|
+
wait_seconds=$(parse_wait_seconds "$text")
|
|
177
|
+
|
|
178
|
+
log "Limit detected. Waiting ${wait_seconds}s before resume."
|
|
179
|
+
send_telegram "Claude Pilot: usage/rate limit detected. Waiting ${wait_seconds}s before sending '${RESUME_COMMAND}'."
|
|
180
|
+
|
|
181
|
+
sleep "$wait_seconds"
|
|
182
|
+
|
|
183
|
+
tmux send-keys -t "$CLAUDE_SESSION" "$RESUME_COMMAND" Enter
|
|
184
|
+
last_resume_epoch=$(date +%s)
|
|
185
|
+
|
|
186
|
+
log "Resume command sent."
|
|
187
|
+
send_telegram "Claude Pilot: resume command sent to tmux session '$CLAUDE_SESSION'."
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
main() {
|
|
191
|
+
require_command tmux
|
|
192
|
+
require_command curl
|
|
193
|
+
|
|
194
|
+
ensure_tmux_session
|
|
195
|
+
|
|
196
|
+
log "Watching tmux session '$CLAUDE_SESSION'. Attach with: tmux attach -t $CLAUDE_SESSION"
|
|
197
|
+
send_telegram "Claude Pilot: watching tmux session '$CLAUDE_SESSION'."
|
|
198
|
+
|
|
199
|
+
while true; do
|
|
200
|
+
if ! tmux has-session -t "$CLAUDE_SESSION" 2>/dev/null; then
|
|
201
|
+
log "tmux session '$CLAUDE_SESSION' ended."
|
|
202
|
+
send_telegram "Claude Pilot: tmux session '$CLAUDE_SESSION' ended."
|
|
203
|
+
exit 0
|
|
204
|
+
fi
|
|
205
|
+
|
|
206
|
+
text=$(capture_pane)
|
|
207
|
+
|
|
208
|
+
if printf '%s' "$text" | grep -Eiq "$LIMIT_REGEX"; then
|
|
209
|
+
handle_limit "$text"
|
|
210
|
+
fi
|
|
211
|
+
|
|
212
|
+
if printf '%s' "$text" | grep -Eiq "$PERMISSION_REGEX"; then
|
|
213
|
+
# Notification only. Do not auto-approve permissions.
|
|
214
|
+
send_telegram "Claude Pilot: Claude may need permission/input in session '$CLAUDE_SESSION'."
|
|
215
|
+
sleep 60
|
|
216
|
+
fi
|
|
217
|
+
|
|
218
|
+
sleep "$CHECK_INTERVAL_SECONDS"
|
|
219
|
+
done
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
main "$@"
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "claude-code-remote-pilot",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Interactive Claude Code supervisor with tmux, Telegram notifications, and auto-resume after usage limits.",
|
|
5
|
+
"type": "commonjs",
|
|
6
|
+
"bin": {
|
|
7
|
+
"claude-remote-pilot": "bin/claude-pilot.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin/",
|
|
11
|
+
"claude-pilot.sh",
|
|
12
|
+
"README.md",
|
|
13
|
+
"LICENSE"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"start": "node bin/claude-pilot.js",
|
|
17
|
+
"pilot": "node bin/claude-pilot.js",
|
|
18
|
+
"check": "node bin/claude-pilot.js --help"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"claude-code",
|
|
22
|
+
"claude",
|
|
23
|
+
"tmux",
|
|
24
|
+
"telegram",
|
|
25
|
+
"agent",
|
|
26
|
+
"auto-resume"
|
|
27
|
+
],
|
|
28
|
+
"author": "mekku",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=18"
|
|
32
|
+
}
|
|
33
|
+
}
|