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.
@@ -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 manager = new SessionManager({ telegram });
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 = (config.load().sessions || []).filter(s => {
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
  });
@@ -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 = new Watcher(session, {
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
- // Claude Code's permission/action prompt UI
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
- // Claude Code shows this footer while actively processing
9
+ // Checked against last 5 lines only — footer disappears when Claude finishes
10
10
  const RUNNING_RE = /esc to interrupt/i;
11
- // Claude Code shows ">" alone on the last line when idle/waiting for next prompt
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 lastLine = text.split('\n').filter(l => l.trim()).pop() || '';
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(text)) {
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(text)) {
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}" "continue" Enter`, { stdio: 'ignore' }); }
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
- module.exports = { load, saveTelegram, saveSessions, clearSessions };
33
+ function saveResumeCommand(cmd) {
34
+ save({ resumeCommand: cmd });
35
+ }
36
+
37
+ module.exports = { load, saveTelegram, saveSessions, clearSessions, saveResumeCommand };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-remote-pilot",
3
- "version": "0.2.8",
3
+ "version": "0.2.9",
4
4
  "description": "Interactive Claude Code supervisor — spawn and monitor multiple Claude sessions from a single terminal.",
5
5
  "type": "commonjs",
6
6
  "bin": {