claude-code-remote-pilot 0.2.2 → 0.2.4

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.
@@ -75,19 +75,29 @@ const C = {
75
75
  reset: '\x1b[0m',
76
76
  green: '\x1b[32m',
77
77
  yellow: '\x1b[33m',
78
+ blue: '\x1b[34m',
79
+ orange: '\x1b[38;5;208m',
78
80
  dim: '\x1b[2m',
79
81
  };
80
82
 
81
83
  function formatStatus(session) {
82
- if (session.status === 'running') return { plain: 'running', colored: `${C.green}running${C.reset}` };
83
- if (session.status === 'limit') {
84
- const secs = session.resumeAt ? Math.max(0, Math.round((session.resumeAt - Date.now()) / 1000)) : 0;
85
- const label = `limit ${Math.ceil(secs / 60)}m`;
86
- return { plain: label, colored: `${C.yellow}${label}${C.reset}` };
84
+ switch (session.status) {
85
+ case 'running':
86
+ return { plain: 'running', colored: `${C.green}running${C.reset}` };
87
+ case 'idle':
88
+ return { plain: 'idle', colored: `${C.blue}idle${C.reset}` };
89
+ case 'needs-response':
90
+ return { plain: 'needs response', colored: `${C.orange}needs response${C.reset}` };
91
+ case 'limit': {
92
+ const secs = session.resumeAt ? Math.max(0, Math.round((session.resumeAt - Date.now()) / 1000)) : 0;
93
+ const label = `limit ${Math.ceil(secs / 60)}m`;
94
+ return { plain: label, colored: `${C.yellow}${label}${C.reset}` };
95
+ }
96
+ case 'ended':
97
+ return { plain: 'ended', colored: `${C.dim}ended${C.reset}` };
98
+ default:
99
+ return { plain: session.status, colored: session.status };
87
100
  }
88
- if (session.status === 'needs-input') return { plain: 'needs input', colored: `${C.yellow}needs input${C.reset}` };
89
- if (session.status === 'ended') return { plain: 'ended', colored: `${C.dim}ended${C.reset}` };
90
- return { plain: session.status, colored: session.status };
91
101
  }
92
102
 
93
103
  function trunc(str, len) {
@@ -110,11 +120,6 @@ function renderTable(sessions) {
110
120
  // ─── watch mode ───────────────────────────────────────────────────────────────
111
121
 
112
122
  function startWatch(manager, rl) {
113
- rl.removeAllListeners('close');
114
- rl.close();
115
- if (process.stdin.isTTY) process.stdin.setRawMode(true);
116
- process.stdin.resume();
117
-
118
123
  function draw() {
119
124
  process.stdout.write('\x1B[2J\x1B[0f');
120
125
  process.stdout.write(renderTable(manager.list()));
@@ -123,18 +128,22 @@ function startWatch(manager, rl) {
123
128
  draw();
124
129
  const interval = setInterval(draw, 2000);
125
130
 
126
- function onData(buf) {
127
- const key = buf.toString();
128
- if (key === 'q' || key === 'Q' || buf[0] === 3) {
129
- clearInterval(interval);
130
- process.stdin.removeListener('data', onData);
131
- if (process.stdin.isTTY) process.stdin.setRawMode(false);
132
- process.stdout.write('\x1B[2J\x1B[0f');
133
- startREPL(manager);
131
+ function exit() {
132
+ process.stdin.removeListener('keypress', onKeypress);
133
+ clearInterval(interval);
134
+ process.stdout.write('\x1B[2J\x1B[0f');
135
+ // Clear any character readline buffered while we were in watch mode
136
+ rl.write(null, { ctrl: true, name: 'u' });
137
+ rl.prompt();
138
+ }
139
+
140
+ function onKeypress(str, key) {
141
+ if (str === 'q' || str === 'Q' || (key && key.ctrl && key.name === 'c')) {
142
+ exit();
134
143
  }
135
144
  }
136
145
 
137
- process.stdin.on('data', onData);
146
+ process.stdin.on('keypress', onKeypress);
138
147
  }
139
148
 
140
149
  // ─── REPL ─────────────────────────────────────────────────────────────────────
package/lib/Watcher.js CHANGED
@@ -4,7 +4,10 @@ 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
- const PERM_RE = /permission|do you want to proceed|approve/i;
7
+ const RESPONSE_RE = /\?\s*$|do you want|shall i|should i|please confirm|yes\/no|\(y\/n\)|approve|allow this/i;
8
+ const PERM_RE = /permission|do you want to proceed|approve this action/i;
9
+ // Claude Code shows ">" or "❯" alone on the last line when idle/waiting for next prompt
10
+ const IDLE_RE = /^\s*[>❯]\s*$/;
8
11
 
9
12
  class Watcher {
10
13
  constructor(session, opts = {}) {
@@ -68,14 +71,20 @@ class Watcher {
68
71
  }
69
72
 
70
73
  const text = this._capture();
74
+ const lastLine = text.split('\n').filter(l => l.trim()).pop() || '';
71
75
 
72
76
  if (LIMIT_RE.test(text)) {
73
77
  await this._handleLimit(text);
74
- } else if (PERM_RE.test(text)) {
75
- if (this.session.status !== 'needs-input') {
76
- this.session.status = 'needs-input';
78
+ } else if (PERM_RE.test(text) || RESPONSE_RE.test(lastLine)) {
79
+ if (this.session.status !== 'needs-response') {
80
+ this.session.status = 'needs-response';
77
81
  notifier.send(this.telegram.token, this.telegram.chatId,
78
- `Pilot: "${this.session.name}" needs input.`);
82
+ `Pilot: "${this.session.name}" needs your response.`);
83
+ }
84
+ } else if (IDLE_RE.test(lastLine)) {
85
+ if (this.session.status !== 'idle') {
86
+ this.session.status = 'idle';
87
+ this.session.resumeAt = null;
79
88
  }
80
89
  } else if (this.session.status !== 'running') {
81
90
  this.session.status = 'running';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-remote-pilot",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "Interactive Claude Code supervisor — spawn and monitor multiple Claude sessions from a single terminal.",
5
5
  "type": "commonjs",
6
6
  "bin": {