heyvm 1.0.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.
Files changed (112) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +621 -0
  3. package/bin/file-browser +0 -0
  4. package/bin/heyvm +0 -0
  5. package/bin/heyvm-core +0 -0
  6. package/bin/heyvm.js +61 -0
  7. package/core/README.md +51 -0
  8. package/core/cmd/heyvm-core/main.go +73 -0
  9. package/core/go.mod +25 -0
  10. package/core/go.sum +47 -0
  11. package/core/internal/auth/errors.go +29 -0
  12. package/core/internal/auth/password.go +187 -0
  13. package/core/internal/auth/provider.go +33 -0
  14. package/core/internal/auth/ssh_key.go +142 -0
  15. package/core/internal/config/config.go +111 -0
  16. package/core/internal/ipc/actions.go +738 -0
  17. package/core/internal/ipc/handler.go +281 -0
  18. package/core/internal/ipc/protocol.go +126 -0
  19. package/core/internal/sftp/errors.go +29 -0
  20. package/core/internal/sftp/manager.go +303 -0
  21. package/core/internal/sftp/types.go +30 -0
  22. package/core/internal/ssh/errors.go +23 -0
  23. package/core/internal/ssh/manager.go +226 -0
  24. package/core/internal/ssh/session.go +105 -0
  25. package/core/internal/vm/errors.go +35 -0
  26. package/core/internal/vm/models.go +84 -0
  27. package/core/internal/vm/registry.go +240 -0
  28. package/package.json +59 -0
  29. package/scripts/install.js +100 -0
  30. package/ui/README.md +43 -0
  31. package/ui/dist/App.d.ts +3 -0
  32. package/ui/dist/App.d.ts.map +1 -0
  33. package/ui/dist/App.js +142 -0
  34. package/ui/dist/App.js.map +1 -0
  35. package/ui/dist/components/ConfirmDialog.d.ts +9 -0
  36. package/ui/dist/components/ConfirmDialog.d.ts.map +1 -0
  37. package/ui/dist/components/ConfirmDialog.js +22 -0
  38. package/ui/dist/components/ConfirmDialog.js.map +1 -0
  39. package/ui/dist/components/ErrorMessage.d.ts +8 -0
  40. package/ui/dist/components/ErrorMessage.d.ts.map +1 -0
  41. package/ui/dist/components/ErrorMessage.js +10 -0
  42. package/ui/dist/components/ErrorMessage.js.map +1 -0
  43. package/ui/dist/components/Header.d.ts +8 -0
  44. package/ui/dist/components/Header.d.ts.map +1 -0
  45. package/ui/dist/components/Header.js +10 -0
  46. package/ui/dist/components/Header.js.map +1 -0
  47. package/ui/dist/components/LoadingSpinner.d.ts +7 -0
  48. package/ui/dist/components/LoadingSpinner.d.ts.map +1 -0
  49. package/ui/dist/components/LoadingSpinner.js +7 -0
  50. package/ui/dist/components/LoadingSpinner.js.map +1 -0
  51. package/ui/dist/components/StatusBar.d.ts +11 -0
  52. package/ui/dist/components/StatusBar.d.ts.map +1 -0
  53. package/ui/dist/components/StatusBar.js +11 -0
  54. package/ui/dist/components/StatusBar.js.map +1 -0
  55. package/ui/dist/core/ipc.d.ts +96 -0
  56. package/ui/dist/core/ipc.d.ts.map +1 -0
  57. package/ui/dist/core/ipc.js +310 -0
  58. package/ui/dist/core/ipc.js.map +1 -0
  59. package/ui/dist/core/types.d.ts +45 -0
  60. package/ui/dist/core/types.d.ts.map +1 -0
  61. package/ui/dist/core/types.js +3 -0
  62. package/ui/dist/core/types.js.map +1 -0
  63. package/ui/dist/hooks/useFiles.d.ts +14 -0
  64. package/ui/dist/hooks/useFiles.d.ts.map +1 -0
  65. package/ui/dist/hooks/useFiles.js +102 -0
  66. package/ui/dist/hooks/useFiles.js.map +1 -0
  67. package/ui/dist/hooks/useVM.d.ts +10 -0
  68. package/ui/dist/hooks/useVM.d.ts.map +1 -0
  69. package/ui/dist/hooks/useVM.js +54 -0
  70. package/ui/dist/hooks/useVM.js.map +1 -0
  71. package/ui/dist/hooks/useVMList.d.ts +10 -0
  72. package/ui/dist/hooks/useVMList.d.ts.map +1 -0
  73. package/ui/dist/hooks/useVMList.js +56 -0
  74. package/ui/dist/hooks/useVMList.js.map +1 -0
  75. package/ui/dist/index.d.ts +3 -0
  76. package/ui/dist/index.d.ts.map +1 -0
  77. package/ui/dist/index.js +7488 -0
  78. package/ui/dist/index.js.map +1 -0
  79. package/ui/dist/keybindings.d.ts +146 -0
  80. package/ui/dist/keybindings.d.ts.map +1 -0
  81. package/ui/dist/keybindings.js +96 -0
  82. package/ui/dist/keybindings.js.map +1 -0
  83. package/ui/dist/screens/AddVMScreen.d.ts +9 -0
  84. package/ui/dist/screens/AddVMScreen.d.ts.map +1 -0
  85. package/ui/dist/screens/AddVMScreen.js +163 -0
  86. package/ui/dist/screens/AddVMScreen.js.map +1 -0
  87. package/ui/dist/screens/VMDetailScreen.d.ts +12 -0
  88. package/ui/dist/screens/VMDetailScreen.d.ts.map +1 -0
  89. package/ui/dist/screens/VMDetailScreen.js +96 -0
  90. package/ui/dist/screens/VMDetailScreen.js.map +1 -0
  91. package/ui/dist/screens/VMListScreen.d.ts +12 -0
  92. package/ui/dist/screens/VMListScreen.d.ts.map +1 -0
  93. package/ui/dist/screens/VMListScreen.js +158 -0
  94. package/ui/dist/screens/VMListScreen.js.map +1 -0
  95. package/ui/dist/screens/tabs/FilesTab.d.ts +9 -0
  96. package/ui/dist/screens/tabs/FilesTab.d.ts.map +1 -0
  97. package/ui/dist/screens/tabs/FilesTab.js +374 -0
  98. package/ui/dist/screens/tabs/FilesTab.js.map +1 -0
  99. package/ui/dist/screens/tabs/OverviewTab.d.ts +10 -0
  100. package/ui/dist/screens/tabs/OverviewTab.d.ts.map +1 -0
  101. package/ui/dist/screens/tabs/OverviewTab.js +110 -0
  102. package/ui/dist/screens/tabs/OverviewTab.js.map +1 -0
  103. package/ui/dist/screens/tabs/TerminalTab.d.ts +9 -0
  104. package/ui/dist/screens/tabs/TerminalTab.d.ts.map +1 -0
  105. package/ui/dist/screens/tabs/TerminalTab.js +270 -0
  106. package/ui/dist/screens/tabs/TerminalTab.js.map +1 -0
  107. package/ui/dist/utils/terminalEmulator.d.ts +49 -0
  108. package/ui/dist/utils/terminalEmulator.d.ts.map +1 -0
  109. package/ui/dist/utils/terminalEmulator.js +88 -0
  110. package/ui/dist/utils/terminalEmulator.js.map +1 -0
  111. package/ui/package.json +34 -0
  112. package/ui/scripts/start-with-core.js +81 -0
@@ -0,0 +1,110 @@
1
+ import React, { useState } from 'react';
2
+ import { Box, Text, useInput } from 'ink';
3
+ import { ipcClient } from '../../core/ipc.js';
4
+ export default React.memo(function OverviewTab({ vm, isActive, onVMUpdated }) {
5
+ const [connecting, setConnecting] = useState(false);
6
+ const [error, setError] = useState(null);
7
+ const handleConnect = async () => {
8
+ try {
9
+ setConnecting(true);
10
+ setError(null);
11
+ await ipcClient.connectVM(vm.id);
12
+ // Refresh VM status after successful connection
13
+ if (onVMUpdated) {
14
+ await onVMUpdated();
15
+ }
16
+ }
17
+ catch (err) {
18
+ setError(err instanceof Error ? err.message : 'Connection failed');
19
+ }
20
+ finally {
21
+ setConnecting(false);
22
+ }
23
+ };
24
+ const handleDisconnect = async () => {
25
+ try {
26
+ setError(null);
27
+ await ipcClient.disconnectVM(vm.id);
28
+ // Refresh VM status after successful disconnection
29
+ if (onVMUpdated) {
30
+ await onVMUpdated();
31
+ }
32
+ }
33
+ catch (err) {
34
+ setError(err instanceof Error ? err.message : 'Disconnect failed');
35
+ }
36
+ };
37
+ useInput((input) => {
38
+ if (!isActive)
39
+ return; // Only handle input when active
40
+ if (input === 'c' && vm.status === 'disconnected') {
41
+ handleConnect();
42
+ }
43
+ if (input === 'd' && vm.status === 'connected') {
44
+ handleDisconnect();
45
+ }
46
+ });
47
+ const getStatusColor = (status) => {
48
+ switch (status) {
49
+ case 'connected':
50
+ return 'green';
51
+ case 'disconnected':
52
+ return 'gray';
53
+ case 'connecting':
54
+ return 'yellow';
55
+ case 'error':
56
+ return 'red';
57
+ default:
58
+ return 'gray';
59
+ }
60
+ };
61
+ return (React.createElement(Box, { flexDirection: "column" },
62
+ error && (React.createElement(Box, { marginBottom: 1 },
63
+ React.createElement(Text, { color: "red" },
64
+ "Error: ",
65
+ error))),
66
+ connecting && (React.createElement(Box, { marginBottom: 1 },
67
+ React.createElement(Text, { color: "yellow" }, "Connecting..."))),
68
+ React.createElement(Box, { flexDirection: "column", marginBottom: 2 },
69
+ React.createElement(Text, { bold: true }, "Connection Details"),
70
+ React.createElement(Box, { marginTop: 1 },
71
+ React.createElement(Box, { width: 16 },
72
+ React.createElement(Text, { dimColor: true }, "Host:")),
73
+ React.createElement(Text, null,
74
+ vm.host,
75
+ ":",
76
+ vm.port)),
77
+ React.createElement(Box, null,
78
+ React.createElement(Box, { width: 16 },
79
+ React.createElement(Text, { dimColor: true }, "Username:")),
80
+ React.createElement(Text, null, vm.username)),
81
+ React.createElement(Box, null,
82
+ React.createElement(Box, { width: 16 },
83
+ React.createElement(Text, { dimColor: true }, "Auth:")),
84
+ React.createElement(Text, { dimColor: true }, vm.auth.type === 'key' ? 'SSH Key' : 'Password')),
85
+ vm.auth.type === 'key' && vm.auth.keyPath && (React.createElement(Box, null,
86
+ React.createElement(Box, { width: 16 },
87
+ React.createElement(Text, { dimColor: true }, "Key:")),
88
+ React.createElement(Text, { dimColor: true }, vm.auth.keyPath)))),
89
+ React.createElement(Box, { flexDirection: "column", marginBottom: 2 },
90
+ React.createElement(Text, { bold: true }, "Status"),
91
+ React.createElement(Box, { marginTop: 1 },
92
+ React.createElement(Box, { width: 16 },
93
+ React.createElement(Text, { dimColor: true }, "Connection:")),
94
+ React.createElement(Text, { bold: true, color: getStatusColor(vm.status) }, vm.status.toUpperCase())),
95
+ React.createElement(Box, null,
96
+ React.createElement(Box, { width: 16 },
97
+ React.createElement(Text, { dimColor: true }, "Last Seen:")),
98
+ React.createElement(Text, { dimColor: true }, vm.lastSeen ? new Date(vm.lastSeen).toLocaleString() : 'Never'))),
99
+ React.createElement(Box, { flexDirection: "column" },
100
+ React.createElement(Box, null,
101
+ vm.status === 'disconnected' && (React.createElement(Text, null,
102
+ "Press ",
103
+ React.createElement(Text, { bold: true, color: "magenta" }, "c"),
104
+ " to connect")),
105
+ vm.status === 'connected' && (React.createElement(Text, null,
106
+ "Press ",
107
+ React.createElement(Text, { bold: true, color: "magenta" }, "d"),
108
+ " to disconnect"))))));
109
+ });
110
+ //# sourceMappingURL=OverviewTab.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OverviewTab.js","sourceRoot":"","sources":["../../../src/screens/tabs/OverviewTab.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ import type { VM } from '../../core/types.js';
3
+ interface TerminalTabProps {
4
+ vm: VM;
5
+ isActive: boolean;
6
+ }
7
+ declare const TerminalTab: React.NamedExoticComponent<TerminalTabProps>;
8
+ export default TerminalTab;
9
+ //# sourceMappingURL=TerminalTab.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TerminalTab.d.ts","sourceRoot":"","sources":["../../../src/screens/tabs/TerminalTab.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAkD,MAAM,OAAO,CAAC;AAEvE,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,qBAAqB,CAAC;AAI9C,UAAU,gBAAgB;IACzB,EAAE,EAAE,EAAE,CAAC;IACP,QAAQ,EAAE,OAAO,CAAC;CAClB;AAKD,QAAA,MAAM,WAAW,8CAyThB,CAAC;AAEF,eAAe,WAAW,CAAC"}
@@ -0,0 +1,270 @@
1
+ import React, { useState, useEffect, useRef, useReducer } from 'react';
2
+ import { Box, Text, useInput } from 'ink';
3
+ import { ipcClient } from '../../core/ipc.js';
4
+ import { TerminalEmulator } from '../../utils/terminalEmulator.js';
5
+ const TERMINAL_ROWS = 24;
6
+ const TERMINAL_COLS = 80;
7
+ const TerminalTab = React.memo(function TerminalTab({ vm, isActive }) {
8
+ const [sessionId, setSessionId] = useState(null);
9
+ const [error, setError] = useState(null);
10
+ const [scrollOffset, setScrollOffset] = useState(0); // 0 = bottom (live), positive = scrolled up
11
+ const ptyStartedRef = useRef(false);
12
+ const emulatorRef = useRef(null);
13
+ // Imperative rendering: use forceUpdate instead of state
14
+ const [, forceUpdate] = useReducer(x => x + 1, 0);
15
+ const dirtyRef = useRef(false);
16
+ const rafIdRef = useRef(null);
17
+ // Cursor blinking
18
+ const [cursorVisible, setCursorVisible] = useState(true);
19
+ const cursorIntervalRef = useRef(null);
20
+ // Initialize terminal emulator
21
+ useEffect(() => {
22
+ emulatorRef.current = new TerminalEmulator(TERMINAL_ROWS, TERMINAL_COLS);
23
+ return () => {
24
+ if (rafIdRef.current !== null) {
25
+ clearTimeout(rafIdRef.current);
26
+ rafIdRef.current = null;
27
+ }
28
+ if (cursorIntervalRef.current) {
29
+ clearInterval(cursorIntervalRef.current);
30
+ cursorIntervalRef.current = null;
31
+ }
32
+ if (emulatorRef.current) {
33
+ emulatorRef.current.dispose();
34
+ emulatorRef.current = null;
35
+ }
36
+ };
37
+ }, []);
38
+ // Cursor blinking effect
39
+ useEffect(() => {
40
+ if (!sessionId || !isActive) {
41
+ if (cursorIntervalRef.current) {
42
+ clearInterval(cursorIntervalRef.current);
43
+ cursorIntervalRef.current = null;
44
+ }
45
+ return;
46
+ }
47
+ // Blink cursor every 500ms
48
+ cursorIntervalRef.current = setInterval(() => {
49
+ setCursorVisible(v => !v);
50
+ }, 500);
51
+ return () => {
52
+ if (cursorIntervalRef.current) {
53
+ clearInterval(cursorIntervalRef.current);
54
+ cursorIntervalRef.current = null;
55
+ }
56
+ };
57
+ }, [sessionId, isActive]);
58
+ // Start PTY session once per VM connection (NOT tied to render lifecycle)
59
+ useEffect(() => {
60
+ if (!isActive)
61
+ return;
62
+ if (vm.status !== 'connected')
63
+ return;
64
+ if (ptyStartedRef.current)
65
+ return;
66
+ console.log('[Terminal] Starting PTY session...');
67
+ ptyStartedRef.current = true;
68
+ // Listen for PTY_READY event
69
+ const handleReady = ({ session_id, vm_id }) => {
70
+ if (vm_id === vm.id) {
71
+ console.log('[Terminal] PTY ready:', session_id);
72
+ setSessionId(session_id);
73
+ }
74
+ };
75
+ const handleError = ({ vm_id, error: errorMsg }) => {
76
+ if (vm_id === vm.id) {
77
+ console.error('[Terminal] PTY error:', errorMsg);
78
+ setError(errorMsg);
79
+ }
80
+ };
81
+ ipcClient.on('PTY_READY', handleReady);
82
+ ipcClient.on('PTY_ERROR', handleError);
83
+ // Send PTY_START message (fire-and-forget)
84
+ ipcClient.startPTY(vm.id, 24, 80);
85
+ // Cleanup only removes event listeners, does NOT close PTY
86
+ return () => {
87
+ ipcClient.off('PTY_READY', handleReady);
88
+ ipcClient.off('PTY_ERROR', handleError);
89
+ };
90
+ }, [isActive, vm.status, vm.id]); // sessionId NOT in deps!
91
+ // Listen for PTY output events - imperative rendering
92
+ useEffect(() => {
93
+ if (!sessionId)
94
+ return;
95
+ const scheduleUpdate = () => {
96
+ if (dirtyRef.current)
97
+ return; // Already scheduled
98
+ dirtyRef.current = true;
99
+ rafIdRef.current = setTimeout(() => {
100
+ dirtyRef.current = false;
101
+ rafIdRef.current = null;
102
+ forceUpdate(); // Trigger re-render of viewport
103
+ }, 16); // ~60fps (16ms)
104
+ };
105
+ const handleOutput = ({ session_id, data }) => {
106
+ if (session_id !== sessionId)
107
+ return;
108
+ // Decode base64 PTY output
109
+ const decoded = Buffer.from(data, 'base64').toString('utf-8');
110
+ // Feed to emulator (optimized - no updateLines() overhead)
111
+ if (emulatorRef.current) {
112
+ emulatorRef.current.write(decoded);
113
+ scheduleUpdate(); // Throttled re-render at ~60fps
114
+ }
115
+ };
116
+ const handleExit = ({ session_id }) => {
117
+ if (session_id === sessionId) {
118
+ console.log('[Terminal] PTY exited');
119
+ setSessionId(null);
120
+ setError('Terminal session closed');
121
+ }
122
+ };
123
+ ipcClient.on('PTY_OUTPUT', handleOutput);
124
+ ipcClient.on('PTY_EXIT', handleExit);
125
+ return () => {
126
+ ipcClient.off('PTY_OUTPUT', handleOutput);
127
+ ipcClient.off('PTY_EXIT', handleExit);
128
+ if (rafIdRef.current !== null) {
129
+ clearTimeout(rafIdRef.current);
130
+ rafIdRef.current = null;
131
+ }
132
+ };
133
+ }, [sessionId]);
134
+ // Handle terminal resize
135
+ useEffect(() => {
136
+ if (!sessionId || !isActive)
137
+ return;
138
+ const handleResize = () => {
139
+ const rows = process.stdout.rows || TERMINAL_ROWS;
140
+ const cols = process.stdout.columns || TERMINAL_COLS;
141
+ // Resize emulator
142
+ if (emulatorRef.current) {
143
+ emulatorRef.current.resize(rows, cols);
144
+ forceUpdate(); // Re-render with new dimensions
145
+ }
146
+ // Notify backend
147
+ ipcClient.resizePTY(vm.id, sessionId, rows, cols);
148
+ };
149
+ process.stdout.on('resize', handleResize);
150
+ return () => {
151
+ process.stdout.off('resize', handleResize);
152
+ };
153
+ }, [sessionId, isActive, vm.id]);
154
+ // Close PTY only on VM disconnect or status change (NOT on tab switch)
155
+ useEffect(() => {
156
+ if (vm.status === 'disconnected' && sessionId) {
157
+ console.log('[Terminal] VM disconnected, closing PTY');
158
+ ipcClient.closePTY(vm.id, sessionId);
159
+ ptyStartedRef.current = false;
160
+ setSessionId(null);
161
+ }
162
+ }, [vm.status, sessionId, vm.id]);
163
+ // Handle all keyboard input - optimized for vim/nano
164
+ useInput((input, key) => {
165
+ if (!isActive || !sessionId || vm.status !== 'connected')
166
+ return;
167
+ // Ctrl+O: Exit terminal to Overview tab (let parent handle it)
168
+ if (key.ctrl && input === 'o') {
169
+ // Don't capture - let it bubble to parent
170
+ return;
171
+ }
172
+ // Scroll handling (intercept before sending to PTY)
173
+ if (key.pageUp) {
174
+ setScrollOffset(prev => Math.min(prev + TERMINAL_ROWS, 1000)); // Scroll up
175
+ return;
176
+ }
177
+ if (key.pageDown) {
178
+ if (scrollOffset > 0) {
179
+ setScrollOffset(prev => Math.max(prev - TERMINAL_ROWS, 0)); // Scroll down
180
+ return;
181
+ }
182
+ // If already at bottom, send to PTY
183
+ }
184
+ // Shift+Up/Down for line-by-line scrolling
185
+ if (key.shift && key.upArrow) {
186
+ setScrollOffset(prev => Math.min(prev + 1, 1000));
187
+ return;
188
+ }
189
+ if (key.shift && key.downArrow) {
190
+ if (scrollOffset > 0) {
191
+ setScrollOffset(prev => Math.max(prev - 1, 0));
192
+ return;
193
+ }
194
+ }
195
+ // Any other key: auto-scroll to bottom (resume live mode)
196
+ if (scrollOffset > 0 && (input || key.return || key.backspace)) {
197
+ setScrollOffset(0);
198
+ }
199
+ // Escape key (critical for vim)
200
+ if (key.escape)
201
+ return ipcClient.writeToPTY(vm.id, sessionId, '\x1b');
202
+ // Ctrl+key combinations
203
+ if (key.ctrl && input) {
204
+ const char = input.toLowerCase();
205
+ if (char >= 'a' && char <= 'z') {
206
+ const code = char.charCodeAt(0) - 96; // Ctrl+A = 1, Ctrl+B = 2, etc
207
+ return ipcClient.writeToPTY(vm.id, sessionId, String.fromCharCode(code));
208
+ }
209
+ }
210
+ // Special keys
211
+ if (key.return)
212
+ return ipcClient.writeToPTY(vm.id, sessionId, '\r');
213
+ if (key.backspace || key.delete)
214
+ return ipcClient.writeToPTY(vm.id, sessionId, '\x7f');
215
+ if (key.tab)
216
+ return ipcClient.writeToPTY(vm.id, sessionId, '\t');
217
+ // Arrow keys (without shift - send to PTY)
218
+ if (key.upArrow && !key.shift)
219
+ return ipcClient.writeToPTY(vm.id, sessionId, '\x1b[A');
220
+ if (key.downArrow && !key.shift)
221
+ return ipcClient.writeToPTY(vm.id, sessionId, '\x1b[B');
222
+ if (key.leftArrow)
223
+ return ipcClient.writeToPTY(vm.id, sessionId, '\x1b[D');
224
+ if (key.rightArrow)
225
+ return ipcClient.writeToPTY(vm.id, sessionId, '\x1b[C');
226
+ // Regular characters (including numbers)
227
+ if (input && !key.ctrl && !key.meta) {
228
+ ipcClient.writeToPTY(vm.id, sessionId, input);
229
+ }
230
+ }, { isActive });
231
+ return (React.createElement(Box, { flexDirection: "column", height: "100%" },
232
+ vm.status !== 'connected' && (React.createElement(Box, { marginBottom: 1 },
233
+ React.createElement(Text, { color: "yellow" }, "\u26A0 Not connected. Switch to Overview tab and connect first."))),
234
+ error && (React.createElement(Box, { marginBottom: 1 },
235
+ React.createElement(Text, { color: "red" },
236
+ "Error: ",
237
+ error))),
238
+ vm.status === 'connected' && emulatorRef.current && (React.createElement(Box, { flexDirection: "column" },
239
+ scrollOffset > 0 && (React.createElement(Box, null,
240
+ React.createElement(Text, { color: "yellow", dimColor: true },
241
+ "\u2191 Scrolled ",
242
+ scrollOffset,
243
+ " lines | PgUp/PgDn or Shift+\u2191\u2193 to scroll | Type to resume"))),
244
+ React.createElement(Box, { flexDirection: "column", height: TERMINAL_ROWS, overflow: "hidden" }, emulatorRef.current.getViewport(TERMINAL_ROWS, scrollOffset).map((line, i) => {
245
+ const cursor = emulatorRef.current?.getCursorPosition();
246
+ const buffer = emulatorRef.current?.getTerminal().buffer.active;
247
+ const viewportY = buffer?.viewportY || 0;
248
+ const cursorRow = cursor ? cursor.y : 0;
249
+ const cursorCol = cursor ? cursor.x : 0;
250
+ // Only show cursor when at bottom (live mode)
251
+ const isOnThisLine = scrollOffset === 0 && (cursorRow - viewportY === i);
252
+ if (isOnThisLine && cursorVisible && sessionId) {
253
+ // Render cursor
254
+ const beforeCursor = line.substring(0, cursorCol);
255
+ const atCursor = line[cursorCol] || ' ';
256
+ const afterCursor = line.substring(cursorCol + 1);
257
+ return (React.createElement(Text, { key: i },
258
+ beforeCursor,
259
+ React.createElement(Text, { inverse: true }, atCursor),
260
+ afterCursor));
261
+ }
262
+ return React.createElement(Text, { key: i }, line || ' ');
263
+ }))))));
264
+ }, (prevProps, nextProps) => {
265
+ return (prevProps.isActive === nextProps.isActive &&
266
+ prevProps.vm.id === nextProps.vm.id &&
267
+ prevProps.vm.status === nextProps.vm.status);
268
+ });
269
+ export default TerminalTab;
270
+ //# sourceMappingURL=TerminalTab.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TerminalTab.js","sourceRoot":"","sources":["../../../src/screens/tabs/TerminalTab.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ import { Terminal } from '@xterm/headless';
2
+ /**
3
+ * Terminal emulator that processes ANSI escape sequences
4
+ * and provides a clean text buffer for rendering
5
+ */
6
+ export declare class TerminalEmulator {
7
+ private terminal;
8
+ constructor(rows?: number, cols?: number);
9
+ /**
10
+ * Write data to the terminal (processes ANSI escape sequences)
11
+ * CRITICAL: Do NOT call updateLines here - it's too slow
12
+ */
13
+ write(data: string): void;
14
+ /**
15
+ * Resize the terminal
16
+ */
17
+ resize(rows: number, cols: number): void;
18
+ /**
19
+ * Get the current screen content as lines of text
20
+ */
21
+ getLines(): string[];
22
+ /**
23
+ * Get the visible viewport (exactly N rows, no more)
24
+ * This returns the actual visible portion of the terminal
25
+ * @param rows Number of rows to return
26
+ * @param scrollOffset Number of lines to scroll up from bottom (0 = live/bottom)
27
+ */
28
+ getViewport(rows?: number, scrollOffset?: number): string[];
29
+ /**
30
+ * Get cursor position
31
+ */
32
+ getCursorPosition(): {
33
+ x: number;
34
+ y: number;
35
+ };
36
+ /**
37
+ * Get the terminal instance (for accessing buffer)
38
+ */
39
+ getTerminal(): Terminal;
40
+ /**
41
+ * Clear the terminal
42
+ */
43
+ clear(): void;
44
+ /**
45
+ * Dispose of the terminal
46
+ */
47
+ dispose(): void;
48
+ }
49
+ //# sourceMappingURL=terminalEmulator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terminalEmulator.d.ts","sourceRoot":"","sources":["../../src/utils/terminalEmulator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAE3C;;;GAGG;AACH,qBAAa,gBAAgB;IAC5B,OAAO,CAAC,QAAQ,CAAW;gBAEf,IAAI,GAAE,MAAW,EAAE,IAAI,GAAE,MAAW;IAYhD;;;OAGG;IACH,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAIzB;;OAEG;IACH,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAIxC;;OAEG;IACH,QAAQ,IAAI,MAAM,EAAE;IAYpB;;;;;OAKG;IACH,WAAW,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,YAAY,GAAE,MAAU,GAAG,MAAM,EAAE;IAgB9D;;OAEG;IACH,iBAAiB,IAAI;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE;IAK7C;;OAEG;IACH,WAAW,IAAI,QAAQ;IAIvB;;OAEG;IACH,KAAK,IAAI,IAAI;IAIb;;OAEG;IACH,OAAO,IAAI,IAAI;CAGf"}
@@ -0,0 +1,88 @@
1
+ import { Terminal } from '@xterm/headless';
2
+ /**
3
+ * Terminal emulator that processes ANSI escape sequences
4
+ * and provides a clean text buffer for rendering
5
+ */
6
+ export class TerminalEmulator {
7
+ constructor(rows = 24, cols = 80) {
8
+ this.terminal = new Terminal({
9
+ rows,
10
+ cols,
11
+ allowProposedApi: true,
12
+ scrollback: 1000,
13
+ });
14
+ // Note: onData is for user input, not needed here
15
+ // Terminal output is processed via write()
16
+ }
17
+ /**
18
+ * Write data to the terminal (processes ANSI escape sequences)
19
+ * CRITICAL: Do NOT call updateLines here - it's too slow
20
+ */
21
+ write(data) {
22
+ this.terminal.write(data);
23
+ }
24
+ /**
25
+ * Resize the terminal
26
+ */
27
+ resize(rows, cols) {
28
+ this.terminal.resize(cols, rows);
29
+ }
30
+ /**
31
+ * Get the current screen content as lines of text
32
+ */
33
+ getLines() {
34
+ const buffer = this.terminal.buffer.active;
35
+ const lines = [];
36
+ for (let i = 0; i < buffer.length; i++) {
37
+ const line = buffer.getLine(i);
38
+ if (line) {
39
+ lines.push(line.translateToString(true));
40
+ }
41
+ }
42
+ return lines;
43
+ }
44
+ /**
45
+ * Get the visible viewport (exactly N rows, no more)
46
+ * This returns the actual visible portion of the terminal
47
+ * @param rows Number of rows to return
48
+ * @param scrollOffset Number of lines to scroll up from bottom (0 = live/bottom)
49
+ */
50
+ getViewport(rows, scrollOffset = 0) {
51
+ const numRows = rows || this.terminal.rows;
52
+ const buffer = this.terminal.buffer.active;
53
+ const viewport = [];
54
+ // Calculate viewport start: go back by scrollOffset from current viewport
55
+ const viewportStart = Math.max(0, buffer.viewportY - scrollOffset);
56
+ for (let i = 0; i < numRows; i++) {
57
+ const line = buffer.getLine(viewportStart + i);
58
+ viewport.push(line ? line.translateToString(true) : '');
59
+ }
60
+ return viewport;
61
+ }
62
+ /**
63
+ * Get cursor position
64
+ */
65
+ getCursorPosition() {
66
+ const buffer = this.terminal.buffer.active;
67
+ return { x: buffer.cursorX, y: buffer.cursorY };
68
+ }
69
+ /**
70
+ * Get the terminal instance (for accessing buffer)
71
+ */
72
+ getTerminal() {
73
+ return this.terminal;
74
+ }
75
+ /**
76
+ * Clear the terminal
77
+ */
78
+ clear() {
79
+ this.terminal.clear();
80
+ }
81
+ /**
82
+ * Dispose of the terminal
83
+ */
84
+ dispose() {
85
+ this.terminal.dispose();
86
+ }
87
+ }
88
+ //# sourceMappingURL=terminalEmulator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terminalEmulator.js","sourceRoot":"","sources":["../../src/utils/terminalEmulator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAE3C;;;GAGG;AACH,MAAM,OAAO,gBAAgB;IAG5B,YAAY,OAAe,EAAE,EAAE,OAAe,EAAE;QAC/C,IAAI,CAAC,QAAQ,GAAG,IAAI,QAAQ,CAAC;YAC5B,IAAI;YACJ,IAAI;YACJ,gBAAgB,EAAE,IAAI;YACtB,UAAU,EAAE,IAAI;SAChB,CAAC,CAAC;QAEH,kDAAkD;QAClD,2CAA2C;IAC5C,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,IAAY;QACjB,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC3B,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,IAAY,EAAE,IAAY;QAChC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAClC,CAAC;IAED;;OAEG;IACH,QAAQ;QACP,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC;QAC3C,MAAM,KAAK,GAAa,EAAE,CAAC;QAC3B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACxC,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;YAC/B,IAAI,IAAI,EAAE,CAAC;gBACV,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC,CAAC;YAC1C,CAAC;QACF,CAAC;QACD,OAAO,KAAK,CAAC;IACd,CAAC;IAED;;;;;OAKG;IACH,WAAW,CAAC,IAAa,EAAE,eAAuB,CAAC;QAClD,MAAM,OAAO,GAAG,IAAI,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;QAC3C,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC;QAC3C,MAAM,QAAQ,GAAa,EAAE,CAAC;QAE9B,0EAA0E;QAC1E,MAAM,aAAa,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,SAAS,GAAG,YAAY,CAAC,CAAC;QAEnE,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,EAAE,CAAC,EAAE,EAAE,CAAC;YAClC,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,aAAa,GAAG,CAAC,CAAC,CAAC;YAC/C,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACzD,CAAC;QAED,OAAO,QAAQ,CAAC;IACjB,CAAC;IAED;;OAEG;IACH,iBAAiB;QAChB,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC;QAC3C,OAAO,EAAE,CAAC,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC;IACjD,CAAC;IAED;;OAEG;IACH,WAAW;QACV,OAAO,IAAI,CAAC,QAAQ,CAAC;IACtB,CAAC;IAED;;OAEG;IACH,KAAK;QACJ,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;IACvB,CAAC;IAED;;OAEG;IACH,OAAO;QACN,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC;IACzB,CAAC;CACD"}
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "heyvm-ui",
3
+ "version": "0.1.0",
4
+ "description": "Interactive TUI for heyvm - SSH VM manager",
5
+ "main": "dist/index.js",
6
+ "type": "module",
7
+ "scripts": {
8
+ "dev": "tsx src/index.tsx",
9
+ "dev:with-core": "npm run build && node scripts/start-with-core.js",
10
+ "build": "tsc && esbuild src/index.tsx --bundle --platform=node --outfile=dist/index.js --external:react --external:ink --format=esm",
11
+ "start": "node dist/index.js",
12
+ "typecheck": "tsc --noEmit"
13
+ },
14
+ "dependencies": {
15
+ "@xterm/headless": "^6.0.0",
16
+ "@xterm/xterm": "^6.0.0",
17
+ "ink": "^5.0.1",
18
+ "ink-select-input": "^6.0.0",
19
+ "ink-text-input": "^6.0.0",
20
+ "react": "^18.3.1",
21
+ "xterm": "^5.3.0",
22
+ "xterm-headless": "^5.3.0"
23
+ },
24
+ "devDependencies": {
25
+ "@types/node": "^22.10.5",
26
+ "@types/react": "^18.3.18",
27
+ "esbuild": "^0.24.2",
28
+ "tsx": "^4.19.2",
29
+ "typescript": "^5.7.2"
30
+ },
31
+ "engines": {
32
+ "node": ">=18.0.0"
33
+ }
34
+ }