claude-remote-cli 3.0.2 → 3.0.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.
- package/dist/bin/claude-remote-cli.js +0 -3
- package/dist/frontend/assets/index-Bw4iKQrv.css +32 -0
- package/dist/frontend/assets/index-C9kPfx3H.js +47 -0
- package/dist/frontend/index.html +2 -2
- package/dist/server/config.js +22 -0
- package/dist/server/git.js +193 -1
- package/dist/server/index.js +51 -292
- package/dist/server/push.js +3 -54
- package/dist/server/sessions.js +265 -180
- package/dist/server/types.js +7 -13
- package/dist/server/workspaces.js +448 -0
- package/dist/server/ws.js +31 -92
- package/dist/test/pr-state.test.js +164 -0
- package/dist/test/pull-requests.test.js +3 -3
- package/dist/test/sessions.test.js +7 -23
- package/package.json +1 -2
- package/dist/frontend/assets/index-Bz_R9N9S.css +0 -32
- package/dist/frontend/assets/index-Yv6LVq28.js +0 -47
- package/dist/server/pty-handler.js +0 -214
- package/dist/server/sdk-handler.js +0 -536
|
@@ -1,214 +0,0 @@
|
|
|
1
|
-
import pty from 'node-pty';
|
|
2
|
-
import fs from 'node:fs';
|
|
3
|
-
import os from 'node:os';
|
|
4
|
-
import path from 'node:path';
|
|
5
|
-
import { AGENT_COMMANDS, AGENT_CONTINUE_ARGS } from './types.js';
|
|
6
|
-
import { readMeta, writeMeta } from './config.js';
|
|
7
|
-
const IDLE_TIMEOUT_MS = 5000;
|
|
8
|
-
const MAX_SCROLLBACK = 256 * 1024; // 256KB max
|
|
9
|
-
export function generateTmuxSessionName(displayName, id) {
|
|
10
|
-
const sanitized = displayName.replace(/[^a-zA-Z0-9-]/g, '-').replace(/-+/g, '-').slice(0, 30);
|
|
11
|
-
return `crc-${sanitized}-${id.slice(0, 8)}`;
|
|
12
|
-
}
|
|
13
|
-
export function resolveTmuxSpawn(command, args, tmuxSessionName) {
|
|
14
|
-
return {
|
|
15
|
-
command: 'tmux',
|
|
16
|
-
args: [
|
|
17
|
-
'-u', 'new-session', '-s', tmuxSessionName, '--', command, ...args,
|
|
18
|
-
';', 'set', 'set-clipboard', 'on',
|
|
19
|
-
';', 'set', 'allow-passthrough', 'on',
|
|
20
|
-
';', 'set', 'mode-keys', 'vi',
|
|
21
|
-
],
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
export function createPtySession(params, sessionsMap, idleChangeCallbacks) {
|
|
25
|
-
const { id, type, agent = 'claude', repoName, repoPath, cwd, root, worktreeName, branchName, displayName, command, args = [], cols = 80, rows = 24, configPath, useTmux: paramUseTmux, tmuxSessionName: paramTmuxSessionName, initialScrollback, restored: paramRestored, } = params;
|
|
26
|
-
const createdAt = new Date().toISOString();
|
|
27
|
-
const resolvedCommand = command || AGENT_COMMANDS[agent];
|
|
28
|
-
// Strip CLAUDECODE env var to allow spawning claude inside a claude-managed server
|
|
29
|
-
const env = Object.assign({}, process.env);
|
|
30
|
-
delete env.CLAUDECODE;
|
|
31
|
-
const useTmux = !command && !!paramUseTmux;
|
|
32
|
-
let spawnCommand = resolvedCommand;
|
|
33
|
-
let spawnArgs = args;
|
|
34
|
-
const tmuxSessionName = paramTmuxSessionName || (useTmux ? generateTmuxSessionName(displayName || repoName || 'session', id) : '');
|
|
35
|
-
if (useTmux) {
|
|
36
|
-
const tmux = resolveTmuxSpawn(resolvedCommand, args, tmuxSessionName);
|
|
37
|
-
spawnCommand = tmux.command;
|
|
38
|
-
spawnArgs = tmux.args;
|
|
39
|
-
}
|
|
40
|
-
const ptyProcess = pty.spawn(spawnCommand, spawnArgs, {
|
|
41
|
-
name: 'xterm-256color',
|
|
42
|
-
cols,
|
|
43
|
-
rows,
|
|
44
|
-
cwd: cwd || repoPath,
|
|
45
|
-
env,
|
|
46
|
-
});
|
|
47
|
-
// Scrollback buffer: stores all PTY output so we can replay on WebSocket (re)connect
|
|
48
|
-
const scrollback = initialScrollback ? [...initialScrollback] : [];
|
|
49
|
-
let scrollbackBytes = initialScrollback ? initialScrollback.reduce((sum, s) => sum + s.length, 0) : 0;
|
|
50
|
-
const resolvedCwd = cwd || repoPath;
|
|
51
|
-
const session = {
|
|
52
|
-
id,
|
|
53
|
-
type: type || 'worktree',
|
|
54
|
-
agent,
|
|
55
|
-
mode: 'pty',
|
|
56
|
-
root: root || '',
|
|
57
|
-
repoName: repoName || '',
|
|
58
|
-
repoPath,
|
|
59
|
-
worktreeName: worktreeName || '',
|
|
60
|
-
branchName: branchName || worktreeName || '',
|
|
61
|
-
displayName: displayName || worktreeName || repoName || '',
|
|
62
|
-
pty: ptyProcess,
|
|
63
|
-
createdAt,
|
|
64
|
-
lastActivity: createdAt,
|
|
65
|
-
scrollback,
|
|
66
|
-
idle: false,
|
|
67
|
-
cwd: resolvedCwd,
|
|
68
|
-
customCommand: command || null,
|
|
69
|
-
useTmux,
|
|
70
|
-
tmuxSessionName,
|
|
71
|
-
onPtyReplacedCallbacks: [],
|
|
72
|
-
status: 'active',
|
|
73
|
-
restored: paramRestored || false,
|
|
74
|
-
};
|
|
75
|
-
sessionsMap.set(id, session);
|
|
76
|
-
// Load existing metadata to preserve a previously-set displayName
|
|
77
|
-
if (configPath && worktreeName) {
|
|
78
|
-
const existing = readMeta(configPath, repoPath);
|
|
79
|
-
if (existing && existing.displayName) {
|
|
80
|
-
session.displayName = existing.displayName;
|
|
81
|
-
}
|
|
82
|
-
writeMeta(configPath, { worktreePath: repoPath, displayName: session.displayName, lastActivity: createdAt });
|
|
83
|
-
}
|
|
84
|
-
let metaFlushTimer = null;
|
|
85
|
-
let idleTimer = null;
|
|
86
|
-
function resetIdleTimer() {
|
|
87
|
-
if (session.idle) {
|
|
88
|
-
session.idle = false;
|
|
89
|
-
for (const cb of idleChangeCallbacks)
|
|
90
|
-
cb(session.id, false);
|
|
91
|
-
}
|
|
92
|
-
if (idleTimer)
|
|
93
|
-
clearTimeout(idleTimer);
|
|
94
|
-
idleTimer = setTimeout(() => {
|
|
95
|
-
if (!session.idle) {
|
|
96
|
-
session.idle = true;
|
|
97
|
-
for (const cb of idleChangeCallbacks)
|
|
98
|
-
cb(session.id, true);
|
|
99
|
-
}
|
|
100
|
-
}, IDLE_TIMEOUT_MS);
|
|
101
|
-
}
|
|
102
|
-
const continueArgs = AGENT_CONTINUE_ARGS[agent];
|
|
103
|
-
function attachHandlers(proc, canRetry) {
|
|
104
|
-
const spawnTime = Date.now();
|
|
105
|
-
// Clear restored flag after 3s of running — means the PTY is healthy
|
|
106
|
-
const restoredClearTimer = session.restored ? setTimeout(() => { session.restored = false; }, 3000) : null;
|
|
107
|
-
proc.onData((data) => {
|
|
108
|
-
session.lastActivity = new Date().toISOString();
|
|
109
|
-
resetIdleTimer();
|
|
110
|
-
scrollback.push(data);
|
|
111
|
-
scrollbackBytes += data.length;
|
|
112
|
-
// Trim oldest entries if over limit
|
|
113
|
-
while (scrollbackBytes > MAX_SCROLLBACK && scrollback.length > 1) {
|
|
114
|
-
scrollbackBytes -= scrollback.shift().length;
|
|
115
|
-
}
|
|
116
|
-
if (configPath && worktreeName && !metaFlushTimer) {
|
|
117
|
-
metaFlushTimer = setTimeout(() => {
|
|
118
|
-
metaFlushTimer = null;
|
|
119
|
-
writeMeta(configPath, { worktreePath: repoPath, displayName: session.displayName, lastActivity: session.lastActivity });
|
|
120
|
-
}, 5000);
|
|
121
|
-
}
|
|
122
|
-
});
|
|
123
|
-
proc.onExit(() => {
|
|
124
|
-
if (canRetry && (Date.now() - spawnTime) < 3000) {
|
|
125
|
-
const retryArgs = args.filter(a => !continueArgs.includes(a));
|
|
126
|
-
const retryNotice = '\r\n[claude-remote-cli] --continue not available; starting new session...\r\n';
|
|
127
|
-
scrollback.length = 0;
|
|
128
|
-
scrollbackBytes = 0;
|
|
129
|
-
scrollback.push(retryNotice);
|
|
130
|
-
scrollbackBytes = retryNotice.length;
|
|
131
|
-
let retryCommand = resolvedCommand;
|
|
132
|
-
let retrySpawnArgs = retryArgs;
|
|
133
|
-
if (useTmux && tmuxSessionName) {
|
|
134
|
-
const retryTmuxName = tmuxSessionName + '-retry';
|
|
135
|
-
session.tmuxSessionName = retryTmuxName;
|
|
136
|
-
const tmux = resolveTmuxSpawn(resolvedCommand, retryArgs, retryTmuxName);
|
|
137
|
-
retryCommand = tmux.command;
|
|
138
|
-
retrySpawnArgs = tmux.args;
|
|
139
|
-
}
|
|
140
|
-
let retryPty;
|
|
141
|
-
try {
|
|
142
|
-
retryPty = pty.spawn(retryCommand, retrySpawnArgs, {
|
|
143
|
-
name: 'xterm-256color',
|
|
144
|
-
cols,
|
|
145
|
-
rows,
|
|
146
|
-
cwd: cwd || repoPath,
|
|
147
|
-
env,
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
catch {
|
|
151
|
-
// Retry spawn failed — fall through to normal exit cleanup
|
|
152
|
-
if (restoredClearTimer)
|
|
153
|
-
clearTimeout(restoredClearTimer);
|
|
154
|
-
if (idleTimer)
|
|
155
|
-
clearTimeout(idleTimer);
|
|
156
|
-
if (metaFlushTimer)
|
|
157
|
-
clearTimeout(metaFlushTimer);
|
|
158
|
-
sessionsMap.delete(id);
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
session.pty = retryPty;
|
|
162
|
-
for (const cb of session.onPtyReplacedCallbacks)
|
|
163
|
-
cb(retryPty);
|
|
164
|
-
attachHandlers(retryPty, false);
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
if (restoredClearTimer)
|
|
168
|
-
clearTimeout(restoredClearTimer);
|
|
169
|
-
// If PTY exited and this is a restored session, mark disconnected rather than delete
|
|
170
|
-
if (session.restored) {
|
|
171
|
-
session.status = 'disconnected';
|
|
172
|
-
session.restored = false; // clear so user-initiated kills can delete normally
|
|
173
|
-
if (idleTimer)
|
|
174
|
-
clearTimeout(idleTimer);
|
|
175
|
-
if (metaFlushTimer)
|
|
176
|
-
clearTimeout(metaFlushTimer);
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
179
|
-
if (idleTimer)
|
|
180
|
-
clearTimeout(idleTimer);
|
|
181
|
-
if (metaFlushTimer)
|
|
182
|
-
clearTimeout(metaFlushTimer);
|
|
183
|
-
if (configPath && worktreeName) {
|
|
184
|
-
writeMeta(configPath, { worktreePath: repoPath, displayName: session.displayName, lastActivity: session.lastActivity });
|
|
185
|
-
}
|
|
186
|
-
sessionsMap.delete(id);
|
|
187
|
-
const tmpDir = path.join(os.tmpdir(), 'claude-remote-cli', id);
|
|
188
|
-
fs.rm(tmpDir, { recursive: true, force: true }, () => { });
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
attachHandlers(ptyProcess, continueArgs.some(a => args.includes(a)));
|
|
192
|
-
const result = {
|
|
193
|
-
id,
|
|
194
|
-
type: session.type,
|
|
195
|
-
agent: session.agent,
|
|
196
|
-
mode: 'pty',
|
|
197
|
-
root: session.root,
|
|
198
|
-
repoName: session.repoName,
|
|
199
|
-
repoPath,
|
|
200
|
-
worktreeName: session.worktreeName,
|
|
201
|
-
branchName: session.branchName,
|
|
202
|
-
displayName: session.displayName,
|
|
203
|
-
pid: ptyProcess.pid,
|
|
204
|
-
createdAt,
|
|
205
|
-
lastActivity: createdAt,
|
|
206
|
-
idle: false,
|
|
207
|
-
cwd: resolvedCwd,
|
|
208
|
-
customCommand: command || null,
|
|
209
|
-
useTmux,
|
|
210
|
-
tmuxSessionName,
|
|
211
|
-
status: 'active',
|
|
212
|
-
};
|
|
213
|
-
return { session, result };
|
|
214
|
-
}
|