claude-code-remote-pilot 0.2.8 → 0.2.9
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/bin/claude-pilot.js +15 -2
- package/lib/SessionManager.js +21 -20
- package/lib/Watcher.js +11 -7
- package/lib/config.js +5 -1
- package/package.json +1 -1
package/bin/claude-pilot.js
CHANGED
|
@@ -182,6 +182,7 @@ const HELP = `
|
|
|
182
182
|
watch Live session monitor (q to exit)
|
|
183
183
|
attach <name> Open tmux session in this terminal
|
|
184
184
|
kill <name> Stop a session
|
|
185
|
+
resume [message] Show or set the message sent after a limit resets
|
|
185
186
|
help Show this help
|
|
186
187
|
exit Quit pilot (asks whether to kill sessions)
|
|
187
188
|
`;
|
|
@@ -240,6 +241,17 @@ function startREPL(manager) {
|
|
|
240
241
|
console.log(` ✓ "${args[0]}" killed.`);
|
|
241
242
|
break;
|
|
242
243
|
}
|
|
244
|
+
case 'resume': {
|
|
245
|
+
if (args.length) {
|
|
246
|
+
const cmd = args.join(' ');
|
|
247
|
+
manager.resumeCommand = cmd;
|
|
248
|
+
config.saveResumeCommand(cmd);
|
|
249
|
+
console.log(` ✓ Resume message saved: "${cmd}"`);
|
|
250
|
+
} else {
|
|
251
|
+
console.log(` Current resume message: "${manager.resumeCommand || '(default)'}"`);
|
|
252
|
+
}
|
|
253
|
+
break;
|
|
254
|
+
}
|
|
243
255
|
case 'help': {
|
|
244
256
|
console.log(HELP);
|
|
245
257
|
break;
|
|
@@ -287,10 +299,11 @@ ${HELP}`);
|
|
|
287
299
|
await ensureDep(setupRl, 'claude', 'Claude Code CLI', 'npm install -g @anthropic-ai/claude-code');
|
|
288
300
|
const telegram = await setupTelegram(setupRl);
|
|
289
301
|
|
|
290
|
-
const
|
|
302
|
+
const cfg = config.load();
|
|
303
|
+
const manager = new SessionManager({ telegram, resumeCommand: cfg.resumeCommand });
|
|
291
304
|
|
|
292
305
|
// Recover sessions from previous run
|
|
293
|
-
const savedSessions = (
|
|
306
|
+
const savedSessions = (cfg.sessions || []).filter(s => {
|
|
294
307
|
try { execSync(`tmux has-session -t "${s.name}"`, { stdio: 'ignore' }); return true; }
|
|
295
308
|
catch { return false; }
|
|
296
309
|
});
|
package/lib/SessionManager.js
CHANGED
|
@@ -4,16 +4,25 @@ const fs = require('fs');
|
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const Watcher = require('./Watcher');
|
|
6
6
|
|
|
7
|
-
const RESERVED = new Set(['spawn', 'list', 'watch', 'attach', 'kill', 'help', 'exit', 'quit']);
|
|
7
|
+
const RESERVED = new Set(['spawn', 'list', 'watch', 'attach', 'kill', 'help', 'exit', 'quit', 'resume']);
|
|
8
8
|
|
|
9
9
|
function sanitizeName(name) {
|
|
10
10
|
return name.replace(/[.:\s]/g, '-');
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
class SessionManager {
|
|
14
|
-
constructor({ telegram = {} } = {}) {
|
|
14
|
+
constructor({ telegram = {}, resumeCommand } = {}) {
|
|
15
15
|
this.sessions = new Map();
|
|
16
16
|
this.telegram = telegram;
|
|
17
|
+
this.resumeCommand = resumeCommand;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
_makeWatcher(session) {
|
|
21
|
+
return new Watcher(session, {
|
|
22
|
+
telegram: this.telegram,
|
|
23
|
+
resumeCommand: this.resumeCommand,
|
|
24
|
+
onEnded: (s) => this.sessions.delete(s.name),
|
|
25
|
+
});
|
|
17
26
|
}
|
|
18
27
|
|
|
19
28
|
spawn(dirPath, name) {
|
|
@@ -32,25 +41,12 @@ class SessionManager {
|
|
|
32
41
|
execSync(`tmux new-session -d -s "${sessionName}" -c "${resolved}" "claude"`, { stdio: 'ignore' });
|
|
33
42
|
|
|
34
43
|
const session = { name: sessionName, path: resolved, status: 'running', startedAt: new Date(), resumeAt: null };
|
|
35
|
-
|
|
36
|
-
const watcher = new Watcher(session, {
|
|
37
|
-
telegram: this.telegram,
|
|
38
|
-
onEnded: (s) => this.sessions.delete(s.name),
|
|
39
|
-
});
|
|
44
|
+
const watcher = this._makeWatcher(session);
|
|
40
45
|
watcher.start();
|
|
41
|
-
|
|
42
46
|
this.sessions.set(sessionName, { session, watcher });
|
|
43
47
|
return session;
|
|
44
48
|
}
|
|
45
49
|
|
|
46
|
-
kill(name) {
|
|
47
|
-
const entry = this.sessions.get(name);
|
|
48
|
-
if (!entry) throw new Error(`Session "${name}" not found.`);
|
|
49
|
-
entry.watcher.stop();
|
|
50
|
-
try { execSync(`tmux kill-session -t "${name}"`, { stdio: 'ignore' }); } catch {}
|
|
51
|
-
this.sessions.delete(name);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
50
|
adopt(name, dirPath) {
|
|
55
51
|
try { execSync(`tmux has-session -t "${name}"`, { stdio: 'ignore' }); }
|
|
56
52
|
catch { throw new Error(`tmux session "${name}" not found.`); }
|
|
@@ -58,15 +54,20 @@ class SessionManager {
|
|
|
58
54
|
if (this.sessions.has(name)) throw new Error(`Session "${name}" already being watched.`);
|
|
59
55
|
|
|
60
56
|
const session = { name, path: dirPath, status: 'running', startedAt: new Date(), resumeAt: null };
|
|
61
|
-
const watcher =
|
|
62
|
-
telegram: this.telegram,
|
|
63
|
-
onEnded: (s) => this.sessions.delete(s.name),
|
|
64
|
-
});
|
|
57
|
+
const watcher = this._makeWatcher(session);
|
|
65
58
|
watcher.start();
|
|
66
59
|
this.sessions.set(name, { session, watcher });
|
|
67
60
|
return session;
|
|
68
61
|
}
|
|
69
62
|
|
|
63
|
+
kill(name) {
|
|
64
|
+
const entry = this.sessions.get(name);
|
|
65
|
+
if (!entry) throw new Error(`Session "${name}" not found.`);
|
|
66
|
+
entry.watcher.stop();
|
|
67
|
+
try { execSync(`tmux kill-session -t "${name}"`, { stdio: 'ignore' }); } catch {}
|
|
68
|
+
this.sessions.delete(name);
|
|
69
|
+
}
|
|
70
|
+
|
|
70
71
|
killAll() {
|
|
71
72
|
for (const name of [...this.sessions.keys()]) {
|
|
72
73
|
try { this.kill(name); } catch {}
|
package/lib/Watcher.js
CHANGED
|
@@ -4,11 +4,11 @@ const crypto = require('crypto');
|
|
|
4
4
|
const notifier = require('./notifier');
|
|
5
5
|
|
|
6
6
|
const LIMIT_RE = /hit your limit|usage limit|rate limit|limit reached|try again|resets/i;
|
|
7
|
-
//
|
|
7
|
+
// Checked against full capture — these messages persist on screen
|
|
8
8
|
const RESPONSE_RE = /do you want to proceed|esc to cancel|ctrl\+e to explain|❯\s*\d+\.\s*yes/i;
|
|
9
|
-
//
|
|
9
|
+
// Checked against last 5 lines only — footer disappears when Claude finishes
|
|
10
10
|
const RUNNING_RE = /esc to interrupt/i;
|
|
11
|
-
//
|
|
11
|
+
// Checked against the single last non-empty line
|
|
12
12
|
const IDLE_RE = /^\s*>\s*$/;
|
|
13
13
|
|
|
14
14
|
class Watcher {
|
|
@@ -20,6 +20,7 @@ class Watcher {
|
|
|
20
20
|
this.fallbackWait = opts.fallbackWait || 300;
|
|
21
21
|
this.cooldown = opts.cooldown || 180;
|
|
22
22
|
this.captureLines = opts.captureLines || 500;
|
|
23
|
+
this.resumeCommand = opts.resumeCommand || 'The usage limit has reset. Please continue where you left off.';
|
|
23
24
|
this.lastHash = '';
|
|
24
25
|
this.lastResumeAt = 0;
|
|
25
26
|
this._timer = null;
|
|
@@ -73,17 +74,20 @@ class Watcher {
|
|
|
73
74
|
}
|
|
74
75
|
|
|
75
76
|
const text = this._capture();
|
|
76
|
-
const
|
|
77
|
+
const nonEmptyLines = text.split('\n').filter(l => l.trim());
|
|
78
|
+
const lastLine = nonEmptyLines[nonEmptyLines.length - 1] || '';
|
|
79
|
+
// Only check recent lines for transient UI elements
|
|
80
|
+
const recentLines = nonEmptyLines.slice(-5).join('\n');
|
|
77
81
|
|
|
78
82
|
if (LIMIT_RE.test(text)) {
|
|
79
83
|
await this._handleLimit(text);
|
|
80
|
-
} else if (RESPONSE_RE.test(
|
|
84
|
+
} else if (RESPONSE_RE.test(recentLines)) {
|
|
81
85
|
if (this.session.status !== 'needs-response') {
|
|
82
86
|
this.session.status = 'needs-response';
|
|
83
87
|
notifier.send(this.telegram.token, this.telegram.chatId,
|
|
84
88
|
`Pilot: "${this.session.name}" needs your response.`);
|
|
85
89
|
}
|
|
86
|
-
} else if (RUNNING_RE.test(
|
|
90
|
+
} else if (RUNNING_RE.test(recentLines)) {
|
|
87
91
|
if (this.session.status !== 'running') {
|
|
88
92
|
this.session.status = 'running';
|
|
89
93
|
this.session.resumeAt = null;
|
|
@@ -117,7 +121,7 @@ class Watcher {
|
|
|
117
121
|
|
|
118
122
|
await new Promise(r => setTimeout(r, wait * 1000));
|
|
119
123
|
|
|
120
|
-
try { execSync(`tmux send-keys -t "${this.session.name}" "
|
|
124
|
+
try { execSync(`tmux send-keys -t "${this.session.name}" "${this.resumeCommand}" Enter`, { stdio: 'ignore' }); }
|
|
121
125
|
catch {}
|
|
122
126
|
|
|
123
127
|
this.lastResumeAt = Date.now() / 1000;
|
package/lib/config.js
CHANGED
|
@@ -30,4 +30,8 @@ function clearSessions() {
|
|
|
30
30
|
save({ sessions: [] });
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
function saveResumeCommand(cmd) {
|
|
34
|
+
save({ resumeCommand: cmd });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
module.exports = { load, saveTelegram, saveSessions, clearSessions, saveResumeCommand };
|
package/package.json
CHANGED