claude-code-remote-pilot 0.2.6 → 0.2.8
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 +30 -1
- package/lib/SessionManager.js +16 -0
- package/lib/Watcher.js +8 -1
- package/lib/config.js +33 -0
- package/package.json +1 -1
package/bin/claude-pilot.js
CHANGED
|
@@ -6,6 +6,7 @@ const path = require('path');
|
|
|
6
6
|
const fs = require('fs');
|
|
7
7
|
const readline = require('readline');
|
|
8
8
|
const SessionManager = require('../lib/SessionManager');
|
|
9
|
+
const config = require('../lib/config');
|
|
9
10
|
|
|
10
11
|
// ─── dependency checks ────────────────────────────────────────────────────────
|
|
11
12
|
|
|
@@ -60,12 +61,18 @@ async function setupTelegram(rl) {
|
|
|
60
61
|
if (process.env.TELEGRAM_BOT_TOKEN && process.env.TELEGRAM_CHAT_ID) {
|
|
61
62
|
return { token: process.env.TELEGRAM_BOT_TOKEN, chatId: process.env.TELEGRAM_CHAT_ID };
|
|
62
63
|
}
|
|
64
|
+
const saved = config.load().telegram;
|
|
65
|
+
if (saved && saved.token && saved.chatId) {
|
|
66
|
+
console.log(' Telegram: using saved config.\n');
|
|
67
|
+
return saved;
|
|
68
|
+
}
|
|
63
69
|
console.log('\nTelegram notifications (optional).');
|
|
64
70
|
const answer = await question(rl, 'Set up Telegram now? (y/n) ');
|
|
65
71
|
if (answer !== 'y' && answer !== 'yes') { console.log('Skipping.\n'); return {}; }
|
|
66
72
|
const token = await questionRaw(rl, 'Bot token: ');
|
|
67
73
|
const chatId = await questionRaw(rl, 'Chat ID: ');
|
|
68
|
-
|
|
74
|
+
config.saveTelegram(token, chatId);
|
|
75
|
+
console.log(' Telegram configured and saved.\n');
|
|
69
76
|
return { token, chatId };
|
|
70
77
|
}
|
|
71
78
|
|
|
@@ -151,14 +158,17 @@ function startWatch(manager, rl) {
|
|
|
151
158
|
async function handleExit(manager, rl) {
|
|
152
159
|
const sessions = manager.list();
|
|
153
160
|
if (!sessions.length) {
|
|
161
|
+
config.clearSessions();
|
|
154
162
|
console.log('');
|
|
155
163
|
process.exit(0);
|
|
156
164
|
}
|
|
157
165
|
const answer = await questionRaw(rl, `\n Kill all ${sessions.length} session(s) before exiting? (y/n) `);
|
|
158
166
|
if (answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes') {
|
|
159
167
|
manager.killAll();
|
|
168
|
+
config.clearSessions();
|
|
160
169
|
console.log(' All sessions killed.\n');
|
|
161
170
|
} else {
|
|
171
|
+
config.saveSessions(sessions);
|
|
162
172
|
console.log(' Sessions keep running. Use tmux to attach.\n');
|
|
163
173
|
}
|
|
164
174
|
process.exit(0);
|
|
@@ -279,6 +289,25 @@ ${HELP}`);
|
|
|
279
289
|
|
|
280
290
|
const manager = new SessionManager({ telegram });
|
|
281
291
|
|
|
292
|
+
// Recover sessions from previous run
|
|
293
|
+
const savedSessions = (config.load().sessions || []).filter(s => {
|
|
294
|
+
try { execSync(`tmux has-session -t "${s.name}"`, { stdio: 'ignore' }); return true; }
|
|
295
|
+
catch { return false; }
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
if (savedSessions.length) {
|
|
299
|
+
console.log(`\n Found ${savedSessions.length} session(s) still running from last time:`);
|
|
300
|
+
savedSessions.forEach(s => console.log(` ${s.name.padEnd(22)} ${s.path}`));
|
|
301
|
+
const recover = await question(setupRl, ' Re-adopt and watch them? (y/n) ');
|
|
302
|
+
if (recover === 'y' || recover === 'yes') {
|
|
303
|
+
savedSessions.forEach(s => {
|
|
304
|
+
try { manager.adopt(s.name, s.path); console.log(` ✓ Re-adopted "${s.name}"`); }
|
|
305
|
+
catch (e) { console.log(` ✗ Could not adopt "${s.name}": ${e.message}`); }
|
|
306
|
+
});
|
|
307
|
+
console.log('');
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
282
311
|
const cwd = process.cwd();
|
|
283
312
|
const defaultName = path.basename(cwd);
|
|
284
313
|
const mount = await question(setupRl, `Mount current directory as a session? (${defaultName}) [y/n] `);
|
package/lib/SessionManager.js
CHANGED
|
@@ -51,6 +51,22 @@ class SessionManager {
|
|
|
51
51
|
this.sessions.delete(name);
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
adopt(name, dirPath) {
|
|
55
|
+
try { execSync(`tmux has-session -t "${name}"`, { stdio: 'ignore' }); }
|
|
56
|
+
catch { throw new Error(`tmux session "${name}" not found.`); }
|
|
57
|
+
|
|
58
|
+
if (this.sessions.has(name)) throw new Error(`Session "${name}" already being watched.`);
|
|
59
|
+
|
|
60
|
+
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
|
+
});
|
|
65
|
+
watcher.start();
|
|
66
|
+
this.sessions.set(name, { session, watcher });
|
|
67
|
+
return session;
|
|
68
|
+
}
|
|
69
|
+
|
|
54
70
|
killAll() {
|
|
55
71
|
for (const name of [...this.sessions.keys()]) {
|
|
56
72
|
try { this.kill(name); } catch {}
|
package/lib/Watcher.js
CHANGED
|
@@ -4,8 +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
|
-
//
|
|
7
|
+
// Claude Code's permission/action prompt UI
|
|
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
|
|
10
|
+
const RUNNING_RE = /esc to interrupt/i;
|
|
9
11
|
// Claude Code shows ">" alone on the last line when idle/waiting for next prompt
|
|
10
12
|
const IDLE_RE = /^\s*>\s*$/;
|
|
11
13
|
|
|
@@ -81,6 +83,11 @@ class Watcher {
|
|
|
81
83
|
notifier.send(this.telegram.token, this.telegram.chatId,
|
|
82
84
|
`Pilot: "${this.session.name}" needs your response.`);
|
|
83
85
|
}
|
|
86
|
+
} else if (RUNNING_RE.test(text)) {
|
|
87
|
+
if (this.session.status !== 'running') {
|
|
88
|
+
this.session.status = 'running';
|
|
89
|
+
this.session.resumeAt = null;
|
|
90
|
+
}
|
|
84
91
|
} else if (IDLE_RE.test(lastLine)) {
|
|
85
92
|
if (this.session.status !== 'idle') {
|
|
86
93
|
this.session.status = 'idle';
|
package/lib/config.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
|
|
6
|
+
const CONFIG_PATH = path.join(os.homedir(), '.claude-remote-pilot.json');
|
|
7
|
+
|
|
8
|
+
function load() {
|
|
9
|
+
try {
|
|
10
|
+
return JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
|
|
11
|
+
} catch {
|
|
12
|
+
return {};
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function save(data) {
|
|
17
|
+
const current = load();
|
|
18
|
+
fs.writeFileSync(CONFIG_PATH, JSON.stringify({ ...current, ...data }, null, 2));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function saveTelegram(token, chatId) {
|
|
22
|
+
save({ telegram: { token, chatId } });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function saveSessions(sessions) {
|
|
26
|
+
save({ sessions: sessions.map(s => ({ name: s.name, path: s.path })) });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function clearSessions() {
|
|
30
|
+
save({ sessions: [] });
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
module.exports = { load, saveTelegram, saveSessions, clearSessions };
|
package/package.json
CHANGED