agent-gear 1.1.1 → 1.1.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.
Files changed (50) hide show
  1. package/AGENTS.md +33 -0
  2. package/dist/acp/bridge.js +2 -2
  3. package/dist/acp/bridge.js.map +1 -1
  4. package/dist/acp/detector.d.ts +3 -0
  5. package/dist/acp/detector.d.ts.map +1 -1
  6. package/dist/acp/detector.js +41 -2
  7. package/dist/acp/detector.js.map +1 -1
  8. package/dist/commands/mcp-proxy.d.ts +8 -0
  9. package/dist/commands/mcp-proxy.d.ts.map +1 -0
  10. package/dist/commands/mcp-proxy.js +105 -0
  11. package/dist/commands/mcp-proxy.js.map +1 -0
  12. package/dist/commands/mcp.d.ts +7 -0
  13. package/dist/commands/mcp.d.ts.map +1 -0
  14. package/dist/commands/mcp.js +33 -0
  15. package/dist/commands/mcp.js.map +1 -0
  16. package/dist/{port-forward.d.ts → gateway.d.ts} +7 -3
  17. package/dist/gateway.d.ts.map +1 -0
  18. package/dist/gateway.js +158 -0
  19. package/dist/gateway.js.map +1 -0
  20. package/dist/gateway.test.d.ts +2 -0
  21. package/dist/gateway.test.d.ts.map +1 -0
  22. package/dist/gateway.test.js +241 -0
  23. package/dist/gateway.test.js.map +1 -0
  24. package/dist/index.js +26 -0
  25. package/dist/index.js.map +1 -1
  26. package/dist/socketio-client.d.ts.map +1 -1
  27. package/dist/socketio-client.js +20 -20
  28. package/dist/socketio-client.js.map +1 -1
  29. package/package.json +1 -1
  30. package/src/acp/bridge.ts +4 -4
  31. package/src/acp/detector.ts +41 -2
  32. package/src/commands/mcp-proxy.ts +128 -0
  33. package/src/commands/mcp.ts +41 -0
  34. package/src/gateway.test.ts +301 -0
  35. package/src/gateway.ts +195 -0
  36. package/src/index.ts +28 -0
  37. package/src/socketio-client.ts +22 -16
  38. package/tests/acp-bridge.test.ts +6 -6
  39. package/tests/acp-detector.test.ts +68 -26
  40. package/tests/commands/mcp-proxy.test.ts +111 -0
  41. package/tests/commands/mcp.test.ts +43 -0
  42. package/dist/port-forward.d.ts.map +0 -1
  43. package/dist/port-forward.js +0 -68
  44. package/dist/port-forward.js.map +0 -1
  45. package/dist/port-forward.test.d.ts +0 -2
  46. package/dist/port-forward.test.d.ts.map +0 -1
  47. package/dist/port-forward.test.js +0 -163
  48. package/dist/port-forward.test.js.map +0 -1
  49. package/src/port-forward.test.ts +0 -202
  50. package/src/port-forward.ts +0 -88
@@ -13,7 +13,7 @@ import { AcpSyncBridge } from './acp/sync-bridge.js';
13
13
  import { gitHandler } from './git-handler.js';
14
14
  import { worktreeHandler } from './worktree-handler.js';
15
15
  import { fsHandler } from './fs-handler.js';
16
- import { portForwardManager } from './port-forward.js';
16
+ import { gatewayManager } from './gateway.js';
17
17
 
18
18
  const CONTROLLER_VERSION = '1.1.1';
19
19
  const acpBridges = new Map<string, AcpSessionBridge>();
@@ -83,12 +83,14 @@ interface AcpResumePayload {
83
83
 
84
84
  interface AcpListSessionsPayload {
85
85
  requestId: string;
86
+ serverType: string;
86
87
  cwd: string;
87
88
  }
88
89
 
89
90
  interface AcpLoadSessionPayload {
90
91
  requestId: string;
91
92
  sessionId: string;
93
+ serverType: string;
92
94
  cwd: string;
93
95
  }
94
96
 
@@ -243,10 +245,10 @@ export function connectToServer(
243
245
  success: true,
244
246
  externalSessionId: payload.acpSessionId,
245
247
  };
246
- if (payload.modes) result.modes = payload.modes;
248
+ result.modes = payload.modes ?? [];
247
249
  if (payload.currentModeId) result.currentModeId = payload.currentModeId;
248
250
  if (payload.currentModel) result.currentModel = payload.currentModel;
249
- if (payload.availableModels) result.availableModels = payload.availableModels;
251
+ result.availableModels = payload.availableModels ?? [];
250
252
  if (payload.currentModelId) result.currentModelId = payload.currentModelId;
251
253
  socket.emit('acp:start:result', result);
252
254
  };
@@ -306,10 +308,10 @@ export function connectToServer(
306
308
  sessionId: data.sessionId,
307
309
  success: true,
308
310
  };
309
- if (payload.modes) result.modes = payload.modes;
311
+ result.modes = payload.modes ?? [];
310
312
  if (payload.currentModeId) result.currentModeId = payload.currentModeId;
311
313
  if (payload.currentModel) result.currentModel = payload.currentModel;
312
- if (payload.availableModels) result.availableModels = payload.availableModels;
314
+ result.availableModels = payload.availableModels ?? [];
313
315
  if (payload.currentModelId) result.currentModelId = payload.currentModelId;
314
316
  socket.emit('acp:resume:result', result);
315
317
  };
@@ -343,8 +345,10 @@ export function connectToServer(
343
345
  });
344
346
 
345
347
  socket.on('acp:list-sessions', async (data: AcpListSessionsPayload) => {
346
- console.log(`[ACP sync] acp:list-sessions received: requestId=${data.requestId} cwd=${data.cwd}`);
347
- const bridge = new AcpSyncBridge({ cwd: data.cwd });
348
+ const serverType = data.serverType ?? 'opencode';
349
+ console.log(`[ACP sync] acp:list-sessions received: requestId=${data.requestId} serverType=${serverType} cwd=${data.cwd}`);
350
+ const spawner = createSpawner(serverType, `sync_${Date.now()}`, data.cwd);
351
+ const bridge = new AcpSyncBridge({ cwd: data.cwd }, spawner);
348
352
  try {
349
353
  await bridge.connect();
350
354
  const result = await bridge.listSessions(data.cwd);
@@ -371,8 +375,10 @@ export function connectToServer(
371
375
  });
372
376
 
373
377
  socket.on('acp:load-session', async (data: AcpLoadSessionPayload) => {
374
- console.log(`[ACP] load-session received: requestId=${data.requestId} sessionId=${data.sessionId} cwd=${data.cwd}`);
375
- const bridge = new AcpSyncBridge({ cwd: data.cwd });
378
+ const serverType = data.serverType ?? 'opencode';
379
+ console.log(`[ACP] load-session received: requestId=${data.requestId} sessionId=${data.sessionId} serverType=${serverType} cwd=${data.cwd}`);
380
+ const spawner = createSpawner(serverType, `sync_${Date.now()}`, data.cwd);
381
+ const bridge = new AcpSyncBridge({ cwd: data.cwd }, spawner);
376
382
  try {
377
383
  await bridge.connect();
378
384
  const { config, updates } = await bridge.loadSession(data.sessionId, data.cwd);
@@ -585,19 +591,19 @@ export function connectToServer(
585
591
  socket.emit('git:commit:result', { requestId: data.requestId, ...result });
586
592
  });
587
593
 
588
- // Port Forward events
594
+ // Port Forward events (event names kept unchanged for backward compatibility)
589
595
  socket.on('port-forward:create', async (data: { tunnelId: string; port: number }) => {
590
- console.log(`Port forward create requested: tunnelId=${data.tunnelId} port=${data.port}`);
591
- await portForwardManager.createTunnel(data.tunnelId, data.port, socket);
596
+ console.log(`Gateway tunnel create requested: tunnelId=${data.tunnelId} port=${data.port}`);
597
+ await gatewayManager.createTunnel(data.tunnelId, data.port, socket);
592
598
  });
593
599
 
594
600
  socket.on('port-forward:data', (data: { tunnelId: string; data: string }) => {
595
- portForwardManager.handleTunnelData(data.tunnelId, data.data);
601
+ gatewayManager.handleTunnelData(data.tunnelId, data.data);
596
602
  });
597
603
 
598
604
  socket.on('port-forward:close', (data: { tunnelId: string }) => {
599
- console.log(`Port forward close: tunnelId=${data.tunnelId}`);
600
- portForwardManager.closeTunnel(data.tunnelId);
605
+ console.log(`Gateway tunnel close: tunnelId=${data.tunnelId}`);
606
+ gatewayManager.closeTunnel(data.tunnelId);
601
607
  });
602
608
 
603
609
  socket.on('acp:permission:response', (data: {
@@ -614,7 +620,7 @@ export function connectToServer(
614
620
  socket.on('disconnect', (reason) => {
615
621
  console.log(`Disconnected: ${reason}`);
616
622
  heartbeat.stop();
617
- portForwardManager.closeAll();
623
+ gatewayManager.closeAll();
618
624
  permissionRequestBridge.clear();
619
625
  });
620
626
 
@@ -536,26 +536,26 @@ describe('AcpSessionBridge', () => {
536
536
  });
537
537
 
538
538
  describe('extractAvailableModels', () => {
539
- it('should return null when models is null', () => {
539
+ it('should return empty array when models is null', () => {
540
540
  const bridge = createBridge();
541
541
 
542
542
  // Access private method via bracket notation
543
543
  const result = (bridge as unknown as Record<string, unknown>).extractAvailableModels(null);
544
- expect(result).toBeNull();
544
+ expect(result).toEqual([]);
545
545
  });
546
546
 
547
- it('should return null when models is undefined', () => {
547
+ it('should return empty array when models is undefined', () => {
548
548
  const bridge = createBridge();
549
549
 
550
550
  const result = (bridge as unknown as Record<string, unknown>).extractAvailableModels(undefined);
551
- expect(result).toBeNull();
551
+ expect(result).toEqual([]);
552
552
  });
553
553
 
554
- it('should return null when availableModels is missing', () => {
554
+ it('should return empty array when availableModels is missing', () => {
555
555
  const bridge = createBridge();
556
556
 
557
557
  const result = (bridge as unknown as Record<string, unknown>).extractAvailableModels({});
558
- expect(result).toBeNull();
558
+ expect(result).toEqual([]);
559
559
  });
560
560
  });
561
561
  });
@@ -32,71 +32,98 @@ describe('AcpDetector', () => {
32
32
  });
33
33
 
34
34
  describe('detect', () => {
35
- it('should detect opencode and github_copilot when both available', async () => {
35
+ it('should detect all 5 servers when all available', async () => {
36
36
  mockExecSuccessOnce('opencode v1.2.3\n');
37
37
  mockExecSuccessOnce('copilot 1.0.0\n');
38
+ mockExecSuccessOnce('claude-agent-acp 0.1.0\n');
39
+ mockExecSuccessOnce('gemini 2.0.0\n');
40
+ mockExecSuccessOnce('codex-acp 0.5.0\n');
38
41
 
39
42
  const result = await detector.detect();
40
43
 
41
- expect(result).toHaveLength(2);
44
+ expect(result).toHaveLength(5);
42
45
  expect(result[0]).toEqual({ type: 'opencode', available: true, version: '1.2.3' });
43
46
  expect(result[1]).toEqual({ type: 'github_copilot', available: true, version: '1.0.0' });
47
+ expect(result[2]).toEqual({ type: 'claude_code', available: true, version: '0.1.0' });
48
+ expect(result[3]).toEqual({ type: 'gemini_cli', available: true, version: '2.0.0' });
49
+ expect(result[4]).toEqual({ type: 'codex', available: true, version: '0.5.0' });
44
50
  });
45
51
 
46
52
  it('should detect as unavailable when command fails', async () => {
47
53
  mockExecFailureOnce();
48
54
  mockExecFailureOnce();
55
+ mockExecFailureOnce();
56
+ mockExecFailureOnce();
57
+ mockExecFailureOnce();
49
58
 
50
59
  const result = await detector.detect();
51
60
 
52
- expect(result).toHaveLength(2);
61
+ expect(result).toHaveLength(5);
53
62
  expect(result[0]).toEqual({ type: 'opencode', available: false });
54
63
  expect(result[1]).toEqual({ type: 'github_copilot', available: false });
64
+ expect(result[2]).toEqual({ type: 'claude_code', available: false });
65
+ expect(result[3]).toEqual({ type: 'gemini_cli', available: false });
66
+ expect(result[4]).toEqual({ type: 'codex', available: false });
55
67
  });
56
68
 
57
- it('should parse version with different formats', async () => {
58
- mockExecSuccessOnce('OpenCode version 2.0.1\n');
59
- mockExecSuccessOnce('copilot 3.0.0\n');
69
+ it('should detect claude_code only when other servers fail', async () => {
70
+ mockExecFailureOnce();
71
+ mockExecFailureOnce();
72
+ mockExecSuccessOnce('claude-agent-acp 0.1.0\n');
73
+ mockExecFailureOnce();
74
+ mockExecFailureOnce();
60
75
 
61
76
  const result = await detector.detect();
62
77
 
63
- expect(result[0].version).toBe('2.0.1');
64
- expect(result[1].version).toBe('3.0.0');
78
+ expect(result).toHaveLength(5);
79
+ expect(result[0]).toEqual({ type: 'opencode', available: false });
80
+ expect(result[1]).toEqual({ type: 'github_copilot', available: false });
81
+ expect(result[2]).toEqual({ type: 'claude_code', available: true, version: '0.1.0' });
82
+ expect(result[3]).toEqual({ type: 'gemini_cli', available: false });
83
+ expect(result[4]).toEqual({ type: 'codex', available: false });
65
84
  });
66
85
 
67
- it('should return unknown version when format not recognized', async () => {
68
- mockExecSuccessOnce('some output without version\n');
69
- mockExecSuccessOnce('copilot output\n');
86
+ it('should detect gemini_cli only when other servers fail', async () => {
87
+ mockExecFailureOnce();
88
+ mockExecFailureOnce();
89
+ mockExecFailureOnce();
90
+ mockExecSuccessOnce('gemini 2.0.0\n');
91
+ mockExecFailureOnce();
70
92
 
71
93
  const result = await detector.detect();
72
94
 
73
- expect(result[0].version).toBe('unknown');
74
- expect(result[1].version).toBe('unknown');
95
+ expect(result).toHaveLength(5);
96
+ expect(result[3]).toEqual({ type: 'gemini_cli', available: true, version: '2.0.0' });
75
97
  });
76
98
 
77
- it('should detect only opencode when copilot fails', async () => {
78
- mockExecSuccessOnce('opencode v1.2.3\n');
99
+ it('should detect codex only when other servers fail', async () => {
100
+ mockExecFailureOnce();
101
+ mockExecFailureOnce();
79
102
  mockExecFailureOnce();
103
+ mockExecFailureOnce();
104
+ mockExecSuccessOnce('codex-acp 0.5.0\n');
80
105
 
81
106
  const result = await detector.detect();
82
107
 
83
- expect(result).toHaveLength(2);
84
- expect(result[0]).toEqual({ type: 'opencode', available: true, version: '1.2.3' });
85
- expect(result[1]).toEqual({ type: 'github_copilot', available: false });
108
+ expect(result).toHaveLength(5);
109
+ expect(result[4]).toEqual({ type: 'codex', available: true, version: '0.5.0' });
86
110
  });
87
111
 
88
- it('should detect only copilot when opencode fails', async () => {
89
- mockExecFailureOnce();
90
- mockExecSuccessOnce('copilot 1.0.0\n');
112
+ it('should parse version with different formats', async () => {
113
+ mockExecSuccessOnce('OpenCode version 2.0.1\n');
114
+ mockExecSuccessOnce('copilot 3.0.0\n');
115
+ mockExecSuccessOnce('v0.1.0\n');
116
+ mockExecSuccessOnce('Gemini 2.0.0\n');
117
+ mockExecSuccessOnce('codex-acp version 0.5.0\n');
91
118
 
92
119
  const result = await detector.detect();
93
120
 
94
- expect(result).toHaveLength(2);
95
- expect(result[0]).toEqual({ type: 'opencode', available: false });
96
- expect(result[1]).toEqual({ type: 'github_copilot', available: true, version: '1.0.0' });
121
+ expect(result[0].version).toBe('2.0.1');
122
+ expect(result[1].version).toBe('3.0.0');
123
+ expect(result[2].version).toBe('0.1.0');
124
+ expect(result[3].version).toBe('2.0.0');
125
+ expect(result[4].version).toBe('0.5.0');
97
126
  });
98
-
99
-
100
127
  });
101
128
 
102
129
  describe('getSpawnerConfig', () => {
@@ -110,6 +137,21 @@ describe('AcpDetector', () => {
110
137
  expect(config).toEqual({ command: 'copilot', args: ['--acp', '--stdio'] });
111
138
  });
112
139
 
140
+ it('should return claude_code spawner config', () => {
141
+ const config = detector.getSpawnerConfig('claude_code');
142
+ expect(config).toEqual({ command: 'claude-agent-acp', args: [] });
143
+ });
144
+
145
+ it('should return gemini_cli spawner config', () => {
146
+ const config = detector.getSpawnerConfig('gemini_cli');
147
+ expect(config).toEqual({ command: 'gemini', args: ['--acp'] });
148
+ });
149
+
150
+ it('should return codex spawner config', () => {
151
+ const config = detector.getSpawnerConfig('codex');
152
+ expect(config).toEqual({ command: 'codex-acp', args: [] });
153
+ });
154
+
113
155
  it('should return opencode as default for unknown serverType', () => {
114
156
  const config = detector.getSpawnerConfig('unknown_server');
115
157
  expect(config).toEqual({ command: 'opencode', args: ['acp'] });
@@ -0,0 +1,111 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+
3
+ vi.mock('child_process', () => ({
4
+ spawn: vi.fn().mockReturnValue({
5
+ stdin: { write: vi.fn() },
6
+ stdout: { on: vi.fn(), removeListener: vi.fn() },
7
+ on: vi.fn(),
8
+ kill: vi.fn(),
9
+ }),
10
+ }));
11
+
12
+ vi.mock('socket.io-client', () => ({
13
+ io: vi.fn().mockReturnValue({
14
+ on: vi.fn(),
15
+ emit: vi.fn(),
16
+ disconnect: vi.fn(),
17
+ connected: true,
18
+ }),
19
+ }));
20
+
21
+ vi.mock('readline', () => ({
22
+ createInterface: vi.fn().mockReturnValue({
23
+ on: vi.fn(),
24
+ }),
25
+ }));
26
+
27
+ import { startMcpRegistration } from '../../src/commands/mcp-proxy.js';
28
+
29
+ beforeEach(() => {
30
+ vi.clearAllMocks();
31
+ vi.stubGlobal('fetch', vi.fn().mockResolvedValue({
32
+ json: () => Promise.resolve({ success: true, proxyId: 'proxy_1', serverId: 'server_1' }),
33
+ }));
34
+ vi.stubGlobal('process', {
35
+ ...process,
36
+ env: { HOSTNAME: 'test-host' },
37
+ on: vi.fn(),
38
+ exit: vi.fn(),
39
+ });
40
+ });
41
+
42
+ describe('mcp-proxy command', () => {
43
+ it('should register with server and spawn child process', async () => {
44
+ const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
45
+
46
+ await startMcpRegistration({
47
+ server: 'http://localhost:4001',
48
+ key: 'mcp_test-key',
49
+ command: 'npx @modelcontextprotocol/server-github',
50
+ });
51
+
52
+ expect(fetch).toHaveBeenCalledWith('http://localhost:4001/api/mcp-proxy/register', {
53
+ method: 'POST',
54
+ headers: { 'Content-Type': 'application/json' },
55
+ body: JSON.stringify({
56
+ key: 'mcp_test-key',
57
+ hostname: 'test-host',
58
+ command: 'npx @modelcontextprotocol/server-github',
59
+ serverName: '@modelcontextprotocol/server-github',
60
+ }),
61
+ });
62
+
63
+ consoleSpy.mockRestore();
64
+ });
65
+
66
+ it('should exit if registration fails', async () => {
67
+ vi.stubGlobal('fetch', vi.fn().mockResolvedValue({
68
+ json: () => Promise.resolve({ success: false, error: 'Invalid key' }),
69
+ }));
70
+ const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
71
+ const processExitSpy = vi.spyOn(process, 'exit').mockImplementation(() => undefined as never);
72
+
73
+ await startMcpRegistration({
74
+ server: 'http://localhost:4001',
75
+ key: 'invalid-key',
76
+ command: 'npx @modelcontextprotocol/server-github',
77
+ });
78
+
79
+ expect(processExitSpy).toHaveBeenCalledWith(1);
80
+
81
+ consoleErrorSpy.mockRestore();
82
+ processExitSpy.mockRestore();
83
+ });
84
+
85
+ it('should use correct option names for CLI parsing', () => {
86
+ // This test verifies that the CLI options are correctly named
87
+ // to avoid conflicts with global options
88
+ const { Command } = require('commander');
89
+ const program = new Command();
90
+
91
+ program
92
+ .command('mcp-proxy')
93
+ .description('Register a local MCP command as a proxy')
94
+ .requiredOption('--mcp-server <endpoint>', 'Server endpoint URL')
95
+ .requiredOption('--mcp-key <key>', 'MCP Proxy Key')
96
+ .requiredOption('--command <command>', 'Local MCP command to proxy');
97
+
98
+ // Verify the command has the correct options
99
+ const cmd = program.commands.find((c: any) => c.name() === 'mcp-proxy');
100
+ expect(cmd).toBeDefined();
101
+
102
+ const options = cmd.options.map((o: any) => o.long);
103
+ expect(options).toContain('--mcp-server');
104
+ expect(options).toContain('--mcp-key');
105
+ expect(options).toContain('--command');
106
+
107
+ // Verify these don't conflict with global options
108
+ expect(options).not.toContain('--key');
109
+ expect(options).not.toContain('--server');
110
+ });
111
+ });
@@ -0,0 +1,43 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+
3
+ vi.mock('readline', () => ({
4
+ createInterface: vi.fn().mockReturnValue({
5
+ on: vi.fn(),
6
+ }),
7
+ }));
8
+
9
+ import { startMcpProxy } from '../../src/commands/mcp.js';
10
+
11
+ beforeEach(() => {
12
+ vi.clearAllMocks();
13
+ vi.stubGlobal('fetch', vi.fn().mockResolvedValue({
14
+ json: () => Promise.resolve({ jsonrpc: '2.0', id: 1, result: { tools: [] } }),
15
+ }));
16
+ vi.stubGlobal('process', {
17
+ ...process,
18
+ stdout: { write: vi.fn() },
19
+ exit: vi.fn(),
20
+ });
21
+ });
22
+
23
+ describe('mcp command', () => {
24
+ it('should start stdio proxy with endpoint and token', async () => {
25
+ const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
26
+ const readline = await import('readline');
27
+ const mockRl = {
28
+ on: vi.fn(),
29
+ };
30
+ vi.mocked(readline.createInterface).mockReturnValue(mockRl as never);
31
+
32
+ await startMcpProxy({
33
+ endpoint: 'http://localhost:3002/mcp/session-1',
34
+ token: 'test-token',
35
+ });
36
+
37
+ expect(readline.createInterface).toHaveBeenCalledWith({ input: process.stdin });
38
+ expect(mockRl.on).toHaveBeenCalledWith('line', expect.any(Function));
39
+ expect(mockRl.on).toHaveBeenCalledWith('close', expect.any(Function));
40
+
41
+ consoleSpy.mockRestore();
42
+ });
43
+ });
@@ -1 +0,0 @@
1
- {"version":3,"file":"port-forward.d.ts","sourceRoot":"","sources":["../src/port-forward.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAQ/C,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,OAAO,CAAkC;IAE3C,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAiDvF,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAStD,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAQnC,QAAQ,IAAI,IAAI;CAOjB;AAED,eAAO,MAAM,kBAAkB,oBAA2B,CAAC"}
@@ -1,68 +0,0 @@
1
- import net from 'node:net';
2
- export class PortForwardManager {
3
- tunnels = new Map();
4
- async createTunnel(tunnelId, port, serverSocket) {
5
- const localSocket = net.connect({ host: '127.0.0.1', port });
6
- return new Promise((resolve) => {
7
- const onError = (err) => {
8
- console.error(`Port forward tunnel error: tunnelId=${tunnelId} error=${err.message}`);
9
- serverSocket.emit('port-forward:created', {
10
- tunnelId,
11
- success: false,
12
- error: err.message,
13
- });
14
- localSocket.destroy();
15
- resolve();
16
- };
17
- localSocket.once('error', onError);
18
- localSocket.once('connect', () => {
19
- localSocket.removeListener('error', onError);
20
- this.tunnels.set(tunnelId, { id: tunnelId, port, localSocket });
21
- console.log(`Port forward tunnel created: tunnelId=${tunnelId} port=${port}`);
22
- serverSocket.emit('port-forward:created', { tunnelId, success: true });
23
- localSocket.on('data', (data) => {
24
- serverSocket.emit('port-forward:data', {
25
- tunnelId,
26
- data: data.toString('base64'),
27
- });
28
- });
29
- localSocket.on('close', () => {
30
- console.log(`Port forward local socket closed: tunnelId=${tunnelId}`);
31
- this.tunnels.delete(tunnelId);
32
- serverSocket.emit('port-forward:close', { tunnelId });
33
- });
34
- localSocket.on('error', (err) => {
35
- console.error(`Port forward local socket error: tunnelId=${tunnelId} error=${err.message}`);
36
- this.tunnels.delete(tunnelId);
37
- serverSocket.emit('port-forward:error', { tunnelId, error: err.message });
38
- });
39
- resolve();
40
- });
41
- });
42
- }
43
- handleTunnelData(tunnelId, data) {
44
- const tunnel = this.tunnels.get(tunnelId);
45
- if (!tunnel) {
46
- console.warn(`Port forward data for unknown tunnel: tunnelId=${tunnelId}`);
47
- return;
48
- }
49
- tunnel.localSocket.write(Buffer.from(data, 'base64'));
50
- }
51
- closeTunnel(tunnelId) {
52
- const tunnel = this.tunnels.get(tunnelId);
53
- if (!tunnel)
54
- return;
55
- console.log(`Port forward closing tunnel: tunnelId=${tunnelId}`);
56
- tunnel.localSocket.destroy();
57
- this.tunnels.delete(tunnelId);
58
- }
59
- closeAll() {
60
- console.log(`Port forward closing all tunnels: count=${this.tunnels.size}`);
61
- for (const tunnel of this.tunnels.values()) {
62
- tunnel.localSocket.destroy();
63
- }
64
- this.tunnels.clear();
65
- }
66
- }
67
- export const portForwardManager = new PortForwardManager();
68
- //# sourceMappingURL=port-forward.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"port-forward.js","sourceRoot":"","sources":["../src/port-forward.ts"],"names":[],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=port-forward.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"port-forward.test.d.ts","sourceRoot":"","sources":["../src/port-forward.test.ts"],"names":[],"mappings":""}