cloudforge-agent 0.1.0
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/LICENSE +21 -0
- package/README.md +125 -0
- package/dist/config.d.ts +35 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +59 -0
- package/dist/config.js.map +1 -0
- package/dist/files.d.ts +84 -0
- package/dist/files.d.ts.map +1 -0
- package/dist/files.js +285 -0
- package/dist/files.js.map +1 -0
- package/dist/git.d.ts +97 -0
- package/dist/git.d.ts.map +1 -0
- package/dist/git.js +320 -0
- package/dist/git.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +80 -0
- package/dist/index.js.map +1 -0
- package/dist/terminal.d.ts +62 -0
- package/dist/terminal.d.ts.map +1 -0
- package/dist/terminal.js +207 -0
- package/dist/terminal.js.map +1 -0
- package/dist/websocket.d.ts +60 -0
- package/dist/websocket.d.ts.map +1 -0
- package/dist/websocket.js +695 -0
- package/dist/websocket.js.map +1 -0
- package/package.json +45 -0
|
@@ -0,0 +1,695 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CloudForge Agent WebSocket Manager
|
|
3
|
+
* Handles connection to CloudForge server
|
|
4
|
+
*/
|
|
5
|
+
import { io } from 'socket.io-client';
|
|
6
|
+
import * as os from 'os';
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
import { getSystemInfo, VERSION } from './config.js';
|
|
9
|
+
export class WebSocketManager {
|
|
10
|
+
socket = null;
|
|
11
|
+
config;
|
|
12
|
+
terminalManager;
|
|
13
|
+
fileManager;
|
|
14
|
+
gitManager;
|
|
15
|
+
reconnectAttempts = 0;
|
|
16
|
+
heartbeatTimer = null;
|
|
17
|
+
isConnected = false;
|
|
18
|
+
constructor(config, terminalManager, fileManager, gitManager) {
|
|
19
|
+
this.config = config;
|
|
20
|
+
this.terminalManager = terminalManager;
|
|
21
|
+
this.fileManager = fileManager;
|
|
22
|
+
this.gitManager = gitManager;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Connect to CloudForge server
|
|
26
|
+
*/
|
|
27
|
+
async connect() {
|
|
28
|
+
return new Promise((resolve, reject) => {
|
|
29
|
+
const socketUrl = `${this.config.serverUrl}/agent`;
|
|
30
|
+
this.socket = io(socketUrl, {
|
|
31
|
+
auth: {
|
|
32
|
+
token: this.config.token,
|
|
33
|
+
},
|
|
34
|
+
transports: ['websocket', 'polling'],
|
|
35
|
+
reconnection: true,
|
|
36
|
+
reconnectionAttempts: Infinity,
|
|
37
|
+
reconnectionDelay: this.config.reconnectDelay,
|
|
38
|
+
reconnectionDelayMax: this.config.maxReconnectDelay,
|
|
39
|
+
timeout: 20000,
|
|
40
|
+
});
|
|
41
|
+
// Connection events
|
|
42
|
+
this.socket.on('connect', () => {
|
|
43
|
+
this.isConnected = true;
|
|
44
|
+
this.reconnectAttempts = 0;
|
|
45
|
+
console.log(chalk.green('Connected to CloudForge!'));
|
|
46
|
+
// Send system info
|
|
47
|
+
this.sendSystemInfo();
|
|
48
|
+
// Start heartbeat
|
|
49
|
+
this.startHeartbeat();
|
|
50
|
+
resolve();
|
|
51
|
+
});
|
|
52
|
+
this.socket.on('connect_error', (err) => {
|
|
53
|
+
if (this.reconnectAttempts === 0) {
|
|
54
|
+
console.error(chalk.red('Connection failed:'), err.message);
|
|
55
|
+
}
|
|
56
|
+
this.reconnectAttempts++;
|
|
57
|
+
if (this.reconnectAttempts >= 5 && !this.isConnected) {
|
|
58
|
+
reject(new Error(`Failed to connect after ${this.reconnectAttempts} attempts: ${err.message}`));
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
this.socket.on('disconnect', (reason) => {
|
|
62
|
+
this.isConnected = false;
|
|
63
|
+
this.stopHeartbeat();
|
|
64
|
+
console.log(chalk.yellow('Disconnected:'), reason);
|
|
65
|
+
if (reason === 'io server disconnect') {
|
|
66
|
+
// Server disconnected us, try to reconnect
|
|
67
|
+
console.log(chalk.yellow('Server disconnected. Reconnecting...'));
|
|
68
|
+
this.socket?.connect();
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
this.socket.on('reconnect', (attemptNumber) => {
|
|
72
|
+
console.log(chalk.green(`Reconnected after ${attemptNumber} attempts`));
|
|
73
|
+
this.sendSystemInfo();
|
|
74
|
+
this.startHeartbeat();
|
|
75
|
+
});
|
|
76
|
+
this.socket.on('reconnect_attempt', (attemptNumber) => {
|
|
77
|
+
if (this.config.debug) {
|
|
78
|
+
console.log(chalk.gray(`Reconnect attempt ${attemptNumber}...`));
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
this.socket.on('error', (err) => {
|
|
82
|
+
console.error(chalk.red('Socket error:'), err);
|
|
83
|
+
});
|
|
84
|
+
// Terminal events from server
|
|
85
|
+
this.setupTerminalHandlers();
|
|
86
|
+
// File events from server
|
|
87
|
+
this.setupFileHandlers();
|
|
88
|
+
// Git events from server
|
|
89
|
+
this.setupGitHandlers();
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Setup terminal event handlers
|
|
94
|
+
*/
|
|
95
|
+
setupTerminalHandlers() {
|
|
96
|
+
if (!this.socket)
|
|
97
|
+
return;
|
|
98
|
+
// Spawn terminal request
|
|
99
|
+
this.socket.on('terminal:spawn', (msg) => {
|
|
100
|
+
if (this.config.debug) {
|
|
101
|
+
console.log(chalk.gray(`Terminal spawn request: ${msg.sessionId}`));
|
|
102
|
+
}
|
|
103
|
+
// Resolve cwd: use provided cwd, or fall back to homeDir
|
|
104
|
+
let cwd = this.config.homeDir;
|
|
105
|
+
if (msg.cwd) {
|
|
106
|
+
let expanded = msg.cwd;
|
|
107
|
+
if (expanded === '~' || expanded.startsWith('~/')) {
|
|
108
|
+
expanded = expanded.replace('~', os.homedir());
|
|
109
|
+
}
|
|
110
|
+
cwd = expanded;
|
|
111
|
+
}
|
|
112
|
+
try {
|
|
113
|
+
const session = this.terminalManager.spawn(msg.sessionId, msg.shell || this.config.shell, msg.cols || 80, msg.rows || 24, cwd);
|
|
114
|
+
// Forward output to server
|
|
115
|
+
session.onData((data) => {
|
|
116
|
+
this.socket?.emit('terminal:output', {
|
|
117
|
+
type: 'terminal:output',
|
|
118
|
+
sessionId: msg.sessionId,
|
|
119
|
+
data,
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
// Forward exit to server
|
|
123
|
+
session.onExit((exitCode) => {
|
|
124
|
+
this.socket?.emit('terminal:closed', {
|
|
125
|
+
type: 'terminal:closed',
|
|
126
|
+
sessionId: msg.sessionId,
|
|
127
|
+
exitCode,
|
|
128
|
+
});
|
|
129
|
+
this.terminalManager.remove(msg.sessionId);
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
catch (err) {
|
|
133
|
+
console.error(chalk.red('Terminal spawn error:'), err);
|
|
134
|
+
this.socket?.emit('terminal:error', {
|
|
135
|
+
type: 'terminal:error',
|
|
136
|
+
sessionId: msg.sessionId,
|
|
137
|
+
error: err instanceof Error ? err.message : String(err),
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
// Terminal input from server
|
|
142
|
+
this.socket.on('terminal:input', (msg) => {
|
|
143
|
+
if (this.config.debug) {
|
|
144
|
+
console.log(chalk.gray(`Terminal input: sessionId=${msg.sessionId}, data length=${msg.data.length}`));
|
|
145
|
+
}
|
|
146
|
+
const session = this.terminalManager.get(msg.sessionId);
|
|
147
|
+
if (session) {
|
|
148
|
+
session.write(msg.data);
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
console.warn(chalk.yellow(`No terminal session found for: ${msg.sessionId}`));
|
|
152
|
+
console.warn(chalk.yellow(`Available sessions: ${this.terminalManager.sessionIds.join(', ')}`));
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
// Terminal resize from server
|
|
156
|
+
this.socket.on('terminal:resize', (msg) => {
|
|
157
|
+
const session = this.terminalManager.get(msg.sessionId);
|
|
158
|
+
if (session) {
|
|
159
|
+
session.resize(msg.cols, msg.rows);
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
// Terminal kill from server
|
|
163
|
+
this.socket.on('terminal:kill', (msg) => {
|
|
164
|
+
this.terminalManager.kill(msg.sessionId);
|
|
165
|
+
});
|
|
166
|
+
// Get terminal cwd
|
|
167
|
+
this.socket.on('terminal:getcwd', async (msg) => {
|
|
168
|
+
const cwd = await this.terminalManager.getCwd(msg.sessionId);
|
|
169
|
+
if (cwd) {
|
|
170
|
+
this.socket?.emit('terminal:cwd', {
|
|
171
|
+
type: 'terminal:cwd',
|
|
172
|
+
sessionId: msg.sessionId,
|
|
173
|
+
cwd,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Setup file event handlers
|
|
180
|
+
*/
|
|
181
|
+
setupFileHandlers() {
|
|
182
|
+
if (!this.socket)
|
|
183
|
+
return;
|
|
184
|
+
// List directory
|
|
185
|
+
this.socket.on('files:list', async (msg) => {
|
|
186
|
+
if (this.config.debug) {
|
|
187
|
+
console.log(chalk.gray(`Files list request: ${msg.path}`));
|
|
188
|
+
}
|
|
189
|
+
try {
|
|
190
|
+
const entries = await this.fileManager.list(msg.path);
|
|
191
|
+
this.socket?.emit('files:list:response', {
|
|
192
|
+
type: 'files:list:response',
|
|
193
|
+
requestId: msg.requestId,
|
|
194
|
+
path: msg.path,
|
|
195
|
+
entries,
|
|
196
|
+
success: true,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
catch (err) {
|
|
200
|
+
console.error(chalk.red('Files list error:'), err);
|
|
201
|
+
this.socket?.emit('files:list:response', {
|
|
202
|
+
type: 'files:list:response',
|
|
203
|
+
requestId: msg.requestId,
|
|
204
|
+
path: msg.path,
|
|
205
|
+
entries: [],
|
|
206
|
+
success: false,
|
|
207
|
+
error: err instanceof Error ? err.message : String(err),
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
// Read file
|
|
212
|
+
this.socket.on('file:read', async (msg) => {
|
|
213
|
+
if (this.config.debug) {
|
|
214
|
+
console.log(chalk.gray(`File read request: ${msg.path}`));
|
|
215
|
+
}
|
|
216
|
+
try {
|
|
217
|
+
const result = await this.fileManager.read(msg.path);
|
|
218
|
+
this.socket?.emit('file:read:response', {
|
|
219
|
+
type: 'file:read:response',
|
|
220
|
+
requestId: msg.requestId,
|
|
221
|
+
...result,
|
|
222
|
+
success: true,
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
catch (err) {
|
|
226
|
+
console.error(chalk.red('File read error:'), err);
|
|
227
|
+
this.socket?.emit('file:read:response', {
|
|
228
|
+
type: 'file:read:response',
|
|
229
|
+
requestId: msg.requestId,
|
|
230
|
+
path: msg.path,
|
|
231
|
+
content: '',
|
|
232
|
+
encoding: 'utf8',
|
|
233
|
+
size: 0,
|
|
234
|
+
isBinary: false,
|
|
235
|
+
success: false,
|
|
236
|
+
error: err instanceof Error ? err.message : String(err),
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
// Write file
|
|
241
|
+
this.socket.on('file:write', async (msg) => {
|
|
242
|
+
if (this.config.debug) {
|
|
243
|
+
console.log(chalk.gray(`File write request: ${msg.path}`));
|
|
244
|
+
}
|
|
245
|
+
try {
|
|
246
|
+
const result = await this.fileManager.write(msg.path, msg.content, msg.encoding || 'utf8');
|
|
247
|
+
this.socket?.emit('file:write:response', {
|
|
248
|
+
type: 'file:write:response',
|
|
249
|
+
requestId: msg.requestId,
|
|
250
|
+
...result,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
catch (err) {
|
|
254
|
+
console.error(chalk.red('File write error:'), err);
|
|
255
|
+
this.socket?.emit('file:write:response', {
|
|
256
|
+
type: 'file:write:response',
|
|
257
|
+
requestId: msg.requestId,
|
|
258
|
+
path: msg.path,
|
|
259
|
+
success: false,
|
|
260
|
+
bytesWritten: 0,
|
|
261
|
+
error: err instanceof Error ? err.message : String(err),
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
// Delete file/directory
|
|
266
|
+
this.socket.on('file:delete', async (msg) => {
|
|
267
|
+
if (this.config.debug) {
|
|
268
|
+
console.log(chalk.gray(`File delete request: ${msg.path}`));
|
|
269
|
+
}
|
|
270
|
+
try {
|
|
271
|
+
const result = await this.fileManager.delete(msg.path, msg.recursive);
|
|
272
|
+
this.socket?.emit('file:delete:response', {
|
|
273
|
+
type: 'file:delete:response',
|
|
274
|
+
requestId: msg.requestId,
|
|
275
|
+
...result,
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
catch (err) {
|
|
279
|
+
console.error(chalk.red('File delete error:'), err);
|
|
280
|
+
this.socket?.emit('file:delete:response', {
|
|
281
|
+
type: 'file:delete:response',
|
|
282
|
+
requestId: msg.requestId,
|
|
283
|
+
path: msg.path,
|
|
284
|
+
success: false,
|
|
285
|
+
error: err instanceof Error ? err.message : String(err),
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
// Create directory
|
|
290
|
+
this.socket.on('file:mkdir', async (msg) => {
|
|
291
|
+
if (this.config.debug) {
|
|
292
|
+
console.log(chalk.gray(`Mkdir request: ${msg.path}`));
|
|
293
|
+
}
|
|
294
|
+
try {
|
|
295
|
+
const result = await this.fileManager.mkdir(msg.path, msg.recursive);
|
|
296
|
+
this.socket?.emit('file:mkdir:response', {
|
|
297
|
+
type: 'file:mkdir:response',
|
|
298
|
+
requestId: msg.requestId,
|
|
299
|
+
...result,
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
catch (err) {
|
|
303
|
+
console.error(chalk.red('Mkdir error:'), err);
|
|
304
|
+
this.socket?.emit('file:mkdir:response', {
|
|
305
|
+
type: 'file:mkdir:response',
|
|
306
|
+
requestId: msg.requestId,
|
|
307
|
+
path: msg.path,
|
|
308
|
+
success: false,
|
|
309
|
+
error: err instanceof Error ? err.message : String(err),
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
// Rename/move file
|
|
314
|
+
this.socket.on('file:rename', async (msg) => {
|
|
315
|
+
if (this.config.debug) {
|
|
316
|
+
console.log(chalk.gray(`Rename request: ${msg.fromPath} -> ${msg.toPath}`));
|
|
317
|
+
}
|
|
318
|
+
try {
|
|
319
|
+
const result = await this.fileManager.rename(msg.fromPath, msg.toPath);
|
|
320
|
+
this.socket?.emit('file:rename:response', {
|
|
321
|
+
type: 'file:rename:response',
|
|
322
|
+
requestId: msg.requestId,
|
|
323
|
+
fromPath: msg.fromPath,
|
|
324
|
+
...result,
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
catch (err) {
|
|
328
|
+
console.error(chalk.red('Rename error:'), err);
|
|
329
|
+
this.socket?.emit('file:rename:response', {
|
|
330
|
+
type: 'file:rename:response',
|
|
331
|
+
requestId: msg.requestId,
|
|
332
|
+
fromPath: msg.fromPath,
|
|
333
|
+
path: msg.toPath,
|
|
334
|
+
success: false,
|
|
335
|
+
error: err instanceof Error ? err.message : String(err),
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
// Stat file/directory
|
|
340
|
+
this.socket.on('file:stat', async (msg) => {
|
|
341
|
+
if (this.config.debug) {
|
|
342
|
+
console.log(chalk.gray(`Stat request: ${msg.path}`));
|
|
343
|
+
}
|
|
344
|
+
try {
|
|
345
|
+
const entry = await this.fileManager.stat(msg.path);
|
|
346
|
+
this.socket?.emit('file:stat:response', {
|
|
347
|
+
type: 'file:stat:response',
|
|
348
|
+
requestId: msg.requestId,
|
|
349
|
+
path: msg.path,
|
|
350
|
+
entry,
|
|
351
|
+
success: entry !== null,
|
|
352
|
+
error: entry === null ? 'File not found' : undefined,
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
catch (err) {
|
|
356
|
+
console.error(chalk.red('Stat error:'), err);
|
|
357
|
+
this.socket?.emit('file:stat:response', {
|
|
358
|
+
type: 'file:stat:response',
|
|
359
|
+
requestId: msg.requestId,
|
|
360
|
+
path: msg.path,
|
|
361
|
+
entry: null,
|
|
362
|
+
success: false,
|
|
363
|
+
error: err instanceof Error ? err.message : String(err),
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Resolve git cwd - handles ~ expansion and field name compatibility
|
|
370
|
+
*/
|
|
371
|
+
resolveGitCwd(msg) {
|
|
372
|
+
let cwd = msg.cwd || msg.path || os.homedir();
|
|
373
|
+
if (cwd === '~' || cwd.startsWith('~/')) {
|
|
374
|
+
cwd = cwd.replace('~', os.homedir());
|
|
375
|
+
}
|
|
376
|
+
return cwd;
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Setup Git event handlers
|
|
380
|
+
*/
|
|
381
|
+
setupGitHandlers() {
|
|
382
|
+
if (!this.socket)
|
|
383
|
+
return;
|
|
384
|
+
// Git status
|
|
385
|
+
this.socket.on('git:status', async (msg) => {
|
|
386
|
+
const cwd = this.resolveGitCwd(msg);
|
|
387
|
+
if (this.config.debug) {
|
|
388
|
+
console.log(chalk.gray(`Git status request: ${cwd}`));
|
|
389
|
+
}
|
|
390
|
+
try {
|
|
391
|
+
const status = await this.gitManager.status(cwd);
|
|
392
|
+
this.socket?.emit('git:status:response', {
|
|
393
|
+
type: 'git:status:response',
|
|
394
|
+
requestId: msg.requestId,
|
|
395
|
+
status,
|
|
396
|
+
success: true,
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
catch (err) {
|
|
400
|
+
console.error(chalk.red('Git status error:'), err);
|
|
401
|
+
this.socket?.emit('git:status:response', {
|
|
402
|
+
type: 'git:status:response',
|
|
403
|
+
requestId: msg.requestId,
|
|
404
|
+
status: null,
|
|
405
|
+
success: false,
|
|
406
|
+
error: err instanceof Error ? err.message : String(err),
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
// Git add (stage files)
|
|
411
|
+
this.socket.on('git:add', async (msg) => {
|
|
412
|
+
const cwd = this.resolveGitCwd(msg);
|
|
413
|
+
if (this.config.debug) {
|
|
414
|
+
console.log(chalk.gray(`Git add request: ${msg.files.join(', ')}`));
|
|
415
|
+
}
|
|
416
|
+
try {
|
|
417
|
+
const result = await this.gitManager.add(cwd, msg.files);
|
|
418
|
+
this.socket?.emit('git:add:response', {
|
|
419
|
+
type: 'git:add:response',
|
|
420
|
+
requestId: msg.requestId,
|
|
421
|
+
...result,
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
catch (err) {
|
|
425
|
+
console.error(chalk.red('Git add error:'), err);
|
|
426
|
+
this.socket?.emit('git:add:response', {
|
|
427
|
+
type: 'git:add:response',
|
|
428
|
+
requestId: msg.requestId,
|
|
429
|
+
success: false,
|
|
430
|
+
error: err instanceof Error ? err.message : String(err),
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
});
|
|
434
|
+
// Git reset (unstage files)
|
|
435
|
+
this.socket.on('git:reset', async (msg) => {
|
|
436
|
+
const cwd = this.resolveGitCwd(msg);
|
|
437
|
+
if (this.config.debug) {
|
|
438
|
+
console.log(chalk.gray(`Git reset request: ${msg.files.join(', ')}`));
|
|
439
|
+
}
|
|
440
|
+
try {
|
|
441
|
+
const result = await this.gitManager.reset(cwd, msg.files);
|
|
442
|
+
this.socket?.emit('git:reset:response', {
|
|
443
|
+
type: 'git:reset:response',
|
|
444
|
+
requestId: msg.requestId,
|
|
445
|
+
...result,
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
catch (err) {
|
|
449
|
+
console.error(chalk.red('Git reset error:'), err);
|
|
450
|
+
this.socket?.emit('git:reset:response', {
|
|
451
|
+
type: 'git:reset:response',
|
|
452
|
+
requestId: msg.requestId,
|
|
453
|
+
success: false,
|
|
454
|
+
error: err instanceof Error ? err.message : String(err),
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
});
|
|
458
|
+
// Git commit
|
|
459
|
+
this.socket.on('git:commit', async (msg) => {
|
|
460
|
+
const cwd = this.resolveGitCwd(msg);
|
|
461
|
+
if (this.config.debug) {
|
|
462
|
+
console.log(chalk.gray(`Git commit request: ${msg.message}`));
|
|
463
|
+
}
|
|
464
|
+
try {
|
|
465
|
+
const result = await this.gitManager.commit(cwd, msg.message);
|
|
466
|
+
this.socket?.emit('git:commit:response', {
|
|
467
|
+
type: 'git:commit:response',
|
|
468
|
+
requestId: msg.requestId,
|
|
469
|
+
...result,
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
catch (err) {
|
|
473
|
+
console.error(chalk.red('Git commit error:'), err);
|
|
474
|
+
this.socket?.emit('git:commit:response', {
|
|
475
|
+
type: 'git:commit:response',
|
|
476
|
+
requestId: msg.requestId,
|
|
477
|
+
success: false,
|
|
478
|
+
error: err instanceof Error ? err.message : String(err),
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
});
|
|
482
|
+
// Git pull
|
|
483
|
+
this.socket.on('git:pull', async (msg) => {
|
|
484
|
+
const cwd = this.resolveGitCwd(msg);
|
|
485
|
+
if (this.config.debug) {
|
|
486
|
+
console.log(chalk.gray('Git pull request'));
|
|
487
|
+
}
|
|
488
|
+
try {
|
|
489
|
+
const result = await this.gitManager.pull(cwd);
|
|
490
|
+
this.socket?.emit('git:pull:response', {
|
|
491
|
+
type: 'git:pull:response',
|
|
492
|
+
requestId: msg.requestId,
|
|
493
|
+
...result,
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
catch (err) {
|
|
497
|
+
console.error(chalk.red('Git pull error:'), err);
|
|
498
|
+
this.socket?.emit('git:pull:response', {
|
|
499
|
+
type: 'git:pull:response',
|
|
500
|
+
requestId: msg.requestId,
|
|
501
|
+
success: false,
|
|
502
|
+
error: err instanceof Error ? err.message : String(err),
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
});
|
|
506
|
+
// Git push
|
|
507
|
+
this.socket.on('git:push', async (msg) => {
|
|
508
|
+
const cwd = this.resolveGitCwd(msg);
|
|
509
|
+
if (this.config.debug) {
|
|
510
|
+
console.log(chalk.gray('Git push request'));
|
|
511
|
+
}
|
|
512
|
+
try {
|
|
513
|
+
const result = await this.gitManager.push(cwd);
|
|
514
|
+
this.socket?.emit('git:push:response', {
|
|
515
|
+
type: 'git:push:response',
|
|
516
|
+
requestId: msg.requestId,
|
|
517
|
+
...result,
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
catch (err) {
|
|
521
|
+
console.error(chalk.red('Git push error:'), err);
|
|
522
|
+
this.socket?.emit('git:push:response', {
|
|
523
|
+
type: 'git:push:response',
|
|
524
|
+
requestId: msg.requestId,
|
|
525
|
+
success: false,
|
|
526
|
+
error: err instanceof Error ? err.message : String(err),
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
});
|
|
530
|
+
// Git diff
|
|
531
|
+
this.socket.on('git:diff', async (msg) => {
|
|
532
|
+
const cwd = this.resolveGitCwd(msg);
|
|
533
|
+
if (this.config.debug) {
|
|
534
|
+
console.log(chalk.gray(`Git diff request: ${msg.file || 'all'}`));
|
|
535
|
+
}
|
|
536
|
+
try {
|
|
537
|
+
const diff = await this.gitManager.diff(cwd, msg.file, msg.staged);
|
|
538
|
+
this.socket?.emit('git:diff:response', {
|
|
539
|
+
type: 'git:diff:response',
|
|
540
|
+
requestId: msg.requestId,
|
|
541
|
+
diff,
|
|
542
|
+
success: true,
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
catch (err) {
|
|
546
|
+
console.error(chalk.red('Git diff error:'), err);
|
|
547
|
+
this.socket?.emit('git:diff:response', {
|
|
548
|
+
type: 'git:diff:response',
|
|
549
|
+
requestId: msg.requestId,
|
|
550
|
+
diff: null,
|
|
551
|
+
success: false,
|
|
552
|
+
error: err instanceof Error ? err.message : String(err),
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
});
|
|
556
|
+
// Git log
|
|
557
|
+
this.socket.on('git:log', async (msg) => {
|
|
558
|
+
const cwd = this.resolveGitCwd(msg);
|
|
559
|
+
if (this.config.debug) {
|
|
560
|
+
console.log(chalk.gray(`Git log request: limit=${msg.limit || 20}`));
|
|
561
|
+
}
|
|
562
|
+
try {
|
|
563
|
+
const logs = await this.gitManager.log(cwd, msg.limit);
|
|
564
|
+
this.socket?.emit('git:log:response', {
|
|
565
|
+
type: 'git:log:response',
|
|
566
|
+
requestId: msg.requestId,
|
|
567
|
+
logs,
|
|
568
|
+
success: true,
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
catch (err) {
|
|
572
|
+
console.error(chalk.red('Git log error:'), err);
|
|
573
|
+
this.socket?.emit('git:log:response', {
|
|
574
|
+
type: 'git:log:response',
|
|
575
|
+
requestId: msg.requestId,
|
|
576
|
+
logs: [],
|
|
577
|
+
success: false,
|
|
578
|
+
error: err instanceof Error ? err.message : String(err),
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
});
|
|
582
|
+
// Git branches
|
|
583
|
+
this.socket.on('git:branches', async (msg) => {
|
|
584
|
+
const cwd = this.resolveGitCwd(msg);
|
|
585
|
+
if (this.config.debug) {
|
|
586
|
+
console.log(chalk.gray('Git branches request'));
|
|
587
|
+
}
|
|
588
|
+
try {
|
|
589
|
+
const result = await this.gitManager.branches(cwd);
|
|
590
|
+
this.socket?.emit('git:branches:response', {
|
|
591
|
+
type: 'git:branches:response',
|
|
592
|
+
requestId: msg.requestId,
|
|
593
|
+
...result,
|
|
594
|
+
success: true,
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
catch (err) {
|
|
598
|
+
console.error(chalk.red('Git branches error:'), err);
|
|
599
|
+
this.socket?.emit('git:branches:response', {
|
|
600
|
+
type: 'git:branches:response',
|
|
601
|
+
requestId: msg.requestId,
|
|
602
|
+
current: '',
|
|
603
|
+
branches: [],
|
|
604
|
+
success: false,
|
|
605
|
+
error: err instanceof Error ? err.message : String(err),
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
});
|
|
609
|
+
// Git checkout (discard changes)
|
|
610
|
+
this.socket.on('git:checkout', async (msg) => {
|
|
611
|
+
const cwd = this.resolveGitCwd(msg);
|
|
612
|
+
if (this.config.debug) {
|
|
613
|
+
console.log(chalk.gray(`Git checkout request: ${msg.files.join(', ')}`));
|
|
614
|
+
}
|
|
615
|
+
try {
|
|
616
|
+
const result = await this.gitManager.checkout(cwd, msg.files);
|
|
617
|
+
this.socket?.emit('git:checkout:response', {
|
|
618
|
+
type: 'git:checkout:response',
|
|
619
|
+
requestId: msg.requestId,
|
|
620
|
+
...result,
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
catch (err) {
|
|
624
|
+
console.error(chalk.red('Git checkout error:'), err);
|
|
625
|
+
this.socket?.emit('git:checkout:response', {
|
|
626
|
+
type: 'git:checkout:response',
|
|
627
|
+
requestId: msg.requestId,
|
|
628
|
+
success: false,
|
|
629
|
+
error: err instanceof Error ? err.message : String(err),
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
/**
|
|
635
|
+
* Send system info to server
|
|
636
|
+
*/
|
|
637
|
+
sendSystemInfo() {
|
|
638
|
+
const systemInfo = getSystemInfo();
|
|
639
|
+
this.socket?.emit('system:info', {
|
|
640
|
+
type: 'system:info',
|
|
641
|
+
os: systemInfo.os,
|
|
642
|
+
hostname: systemInfo.hostname,
|
|
643
|
+
version: VERSION,
|
|
644
|
+
shell: systemInfo.shell,
|
|
645
|
+
homeDir: systemInfo.homeDir,
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
/**
|
|
649
|
+
* Start heartbeat timer
|
|
650
|
+
*/
|
|
651
|
+
startHeartbeat() {
|
|
652
|
+
this.stopHeartbeat();
|
|
653
|
+
this.heartbeatTimer = setInterval(() => {
|
|
654
|
+
if (!this.isConnected)
|
|
655
|
+
return;
|
|
656
|
+
const systemInfo = getSystemInfo();
|
|
657
|
+
this.socket?.emit('heartbeat', {
|
|
658
|
+
type: 'heartbeat',
|
|
659
|
+
timestamp: Date.now(),
|
|
660
|
+
os: systemInfo.os,
|
|
661
|
+
hostname: systemInfo.hostname,
|
|
662
|
+
version: VERSION,
|
|
663
|
+
uptime: process.uptime(),
|
|
664
|
+
});
|
|
665
|
+
if (this.config.debug) {
|
|
666
|
+
console.log(chalk.gray('Heartbeat sent'));
|
|
667
|
+
}
|
|
668
|
+
}, this.config.heartbeatInterval);
|
|
669
|
+
}
|
|
670
|
+
/**
|
|
671
|
+
* Stop heartbeat timer
|
|
672
|
+
*/
|
|
673
|
+
stopHeartbeat() {
|
|
674
|
+
if (this.heartbeatTimer) {
|
|
675
|
+
clearInterval(this.heartbeatTimer);
|
|
676
|
+
this.heartbeatTimer = null;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* Disconnect from server
|
|
681
|
+
*/
|
|
682
|
+
disconnect() {
|
|
683
|
+
this.stopHeartbeat();
|
|
684
|
+
this.socket?.disconnect();
|
|
685
|
+
this.socket = null;
|
|
686
|
+
this.isConnected = false;
|
|
687
|
+
}
|
|
688
|
+
/**
|
|
689
|
+
* Check if connected
|
|
690
|
+
*/
|
|
691
|
+
get connected() {
|
|
692
|
+
return this.isConnected;
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
//# sourceMappingURL=websocket.js.map
|