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 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
+ });
@@ -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
+ }