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.
- package/LICENSE +21 -0
- package/README.md +621 -0
- package/bin/file-browser +0 -0
- package/bin/heyvm +0 -0
- package/bin/heyvm-core +0 -0
- package/bin/heyvm.js +61 -0
- package/core/README.md +51 -0
- package/core/cmd/heyvm-core/main.go +73 -0
- package/core/go.mod +25 -0
- package/core/go.sum +47 -0
- package/core/internal/auth/errors.go +29 -0
- package/core/internal/auth/password.go +187 -0
- package/core/internal/auth/provider.go +33 -0
- package/core/internal/auth/ssh_key.go +142 -0
- package/core/internal/config/config.go +111 -0
- package/core/internal/ipc/actions.go +738 -0
- package/core/internal/ipc/handler.go +281 -0
- package/core/internal/ipc/protocol.go +126 -0
- package/core/internal/sftp/errors.go +29 -0
- package/core/internal/sftp/manager.go +303 -0
- package/core/internal/sftp/types.go +30 -0
- package/core/internal/ssh/errors.go +23 -0
- package/core/internal/ssh/manager.go +226 -0
- package/core/internal/ssh/session.go +105 -0
- package/core/internal/vm/errors.go +35 -0
- package/core/internal/vm/models.go +84 -0
- package/core/internal/vm/registry.go +240 -0
- package/package.json +59 -0
- package/scripts/install.js +100 -0
- package/ui/README.md +43 -0
- package/ui/dist/App.d.ts +3 -0
- package/ui/dist/App.d.ts.map +1 -0
- package/ui/dist/App.js +142 -0
- package/ui/dist/App.js.map +1 -0
- package/ui/dist/components/ConfirmDialog.d.ts +9 -0
- package/ui/dist/components/ConfirmDialog.d.ts.map +1 -0
- package/ui/dist/components/ConfirmDialog.js +22 -0
- package/ui/dist/components/ConfirmDialog.js.map +1 -0
- package/ui/dist/components/ErrorMessage.d.ts +8 -0
- package/ui/dist/components/ErrorMessage.d.ts.map +1 -0
- package/ui/dist/components/ErrorMessage.js +10 -0
- package/ui/dist/components/ErrorMessage.js.map +1 -0
- package/ui/dist/components/Header.d.ts +8 -0
- package/ui/dist/components/Header.d.ts.map +1 -0
- package/ui/dist/components/Header.js +10 -0
- package/ui/dist/components/Header.js.map +1 -0
- package/ui/dist/components/LoadingSpinner.d.ts +7 -0
- package/ui/dist/components/LoadingSpinner.d.ts.map +1 -0
- package/ui/dist/components/LoadingSpinner.js +7 -0
- package/ui/dist/components/LoadingSpinner.js.map +1 -0
- package/ui/dist/components/StatusBar.d.ts +11 -0
- package/ui/dist/components/StatusBar.d.ts.map +1 -0
- package/ui/dist/components/StatusBar.js +11 -0
- package/ui/dist/components/StatusBar.js.map +1 -0
- package/ui/dist/core/ipc.d.ts +96 -0
- package/ui/dist/core/ipc.d.ts.map +1 -0
- package/ui/dist/core/ipc.js +310 -0
- package/ui/dist/core/ipc.js.map +1 -0
- package/ui/dist/core/types.d.ts +45 -0
- package/ui/dist/core/types.d.ts.map +1 -0
- package/ui/dist/core/types.js +3 -0
- package/ui/dist/core/types.js.map +1 -0
- package/ui/dist/hooks/useFiles.d.ts +14 -0
- package/ui/dist/hooks/useFiles.d.ts.map +1 -0
- package/ui/dist/hooks/useFiles.js +102 -0
- package/ui/dist/hooks/useFiles.js.map +1 -0
- package/ui/dist/hooks/useVM.d.ts +10 -0
- package/ui/dist/hooks/useVM.d.ts.map +1 -0
- package/ui/dist/hooks/useVM.js +54 -0
- package/ui/dist/hooks/useVM.js.map +1 -0
- package/ui/dist/hooks/useVMList.d.ts +10 -0
- package/ui/dist/hooks/useVMList.d.ts.map +1 -0
- package/ui/dist/hooks/useVMList.js +56 -0
- package/ui/dist/hooks/useVMList.js.map +1 -0
- package/ui/dist/index.d.ts +3 -0
- package/ui/dist/index.d.ts.map +1 -0
- package/ui/dist/index.js +7488 -0
- package/ui/dist/index.js.map +1 -0
- package/ui/dist/keybindings.d.ts +146 -0
- package/ui/dist/keybindings.d.ts.map +1 -0
- package/ui/dist/keybindings.js +96 -0
- package/ui/dist/keybindings.js.map +1 -0
- package/ui/dist/screens/AddVMScreen.d.ts +9 -0
- package/ui/dist/screens/AddVMScreen.d.ts.map +1 -0
- package/ui/dist/screens/AddVMScreen.js +163 -0
- package/ui/dist/screens/AddVMScreen.js.map +1 -0
- package/ui/dist/screens/VMDetailScreen.d.ts +12 -0
- package/ui/dist/screens/VMDetailScreen.d.ts.map +1 -0
- package/ui/dist/screens/VMDetailScreen.js +96 -0
- package/ui/dist/screens/VMDetailScreen.js.map +1 -0
- package/ui/dist/screens/VMListScreen.d.ts +12 -0
- package/ui/dist/screens/VMListScreen.d.ts.map +1 -0
- package/ui/dist/screens/VMListScreen.js +158 -0
- package/ui/dist/screens/VMListScreen.js.map +1 -0
- package/ui/dist/screens/tabs/FilesTab.d.ts +9 -0
- package/ui/dist/screens/tabs/FilesTab.d.ts.map +1 -0
- package/ui/dist/screens/tabs/FilesTab.js +374 -0
- package/ui/dist/screens/tabs/FilesTab.js.map +1 -0
- package/ui/dist/screens/tabs/OverviewTab.d.ts +10 -0
- package/ui/dist/screens/tabs/OverviewTab.d.ts.map +1 -0
- package/ui/dist/screens/tabs/OverviewTab.js +110 -0
- package/ui/dist/screens/tabs/OverviewTab.js.map +1 -0
- package/ui/dist/screens/tabs/TerminalTab.d.ts +9 -0
- package/ui/dist/screens/tabs/TerminalTab.d.ts.map +1 -0
- package/ui/dist/screens/tabs/TerminalTab.js +270 -0
- package/ui/dist/screens/tabs/TerminalTab.js.map +1 -0
- package/ui/dist/utils/terminalEmulator.d.ts +49 -0
- package/ui/dist/utils/terminalEmulator.d.ts.map +1 -0
- package/ui/dist/utils/terminalEmulator.js +88 -0
- package/ui/dist/utils/terminalEmulator.js.map +1 -0
- package/ui/package.json +34 -0
- package/ui/scripts/start-with-core.js +81 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
package vm
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"time"
|
|
5
|
+
)
|
|
6
|
+
|
|
7
|
+
// VMStatus represents the current state of a VM connection
|
|
8
|
+
type VMStatus string
|
|
9
|
+
|
|
10
|
+
const (
|
|
11
|
+
VMStatusUnknown VMStatus = "unknown"
|
|
12
|
+
VMStatusConnected VMStatus = "connected"
|
|
13
|
+
VMStatusDisconnected VMStatus = "disconnected"
|
|
14
|
+
VMStatusConnecting VMStatus = "connecting"
|
|
15
|
+
VMStatusError VMStatus = "error"
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
// AuthType represents the authentication method
|
|
19
|
+
type AuthType string
|
|
20
|
+
|
|
21
|
+
const (
|
|
22
|
+
AuthTypeKey AuthType = "key"
|
|
23
|
+
AuthTypePassword AuthType = "password"
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
// AuthConfig contains authentication configuration for a VM
|
|
27
|
+
type AuthConfig struct {
|
|
28
|
+
Type AuthType `yaml:"type" json:"type"`
|
|
29
|
+
KeyPath string `yaml:"key_path,omitempty" json:"keyPath,omitempty"`
|
|
30
|
+
RememberPassword bool `yaml:"remember_password" json:"rememberPassword"`
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// VM represents a virtual machine configuration
|
|
34
|
+
type VM struct {
|
|
35
|
+
ID string `yaml:"id" json:"id"`
|
|
36
|
+
Name string `yaml:"name" json:"name"`
|
|
37
|
+
Host string `yaml:"host" json:"host"`
|
|
38
|
+
Port int `yaml:"port" json:"port"`
|
|
39
|
+
Username string `yaml:"username" json:"username"`
|
|
40
|
+
Auth AuthConfig `yaml:"auth" json:"auth"`
|
|
41
|
+
LastSeen time.Time `yaml:"last_seen" json:"lastSeen"`
|
|
42
|
+
Status VMStatus `yaml:"status" json:"status"`
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Validate checks if the VM configuration is valid
|
|
46
|
+
func (v *VM) Validate() error {
|
|
47
|
+
if v.Name == "" {
|
|
48
|
+
return ErrInvalidVMName
|
|
49
|
+
}
|
|
50
|
+
if v.Host == "" {
|
|
51
|
+
return ErrInvalidVMHost
|
|
52
|
+
}
|
|
53
|
+
if v.Port <= 0 || v.Port > 65535 {
|
|
54
|
+
return ErrInvalidVMPort
|
|
55
|
+
}
|
|
56
|
+
if v.Username == "" {
|
|
57
|
+
return ErrInvalidVMUsername
|
|
58
|
+
}
|
|
59
|
+
if v.Auth.Type != AuthTypeKey && v.Auth.Type != AuthTypePassword {
|
|
60
|
+
return ErrInvalidAuthType
|
|
61
|
+
}
|
|
62
|
+
if v.Auth.Type == AuthTypeKey && v.Auth.KeyPath == "" {
|
|
63
|
+
return ErrMissingKeyPath
|
|
64
|
+
}
|
|
65
|
+
return nil
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Clone creates a deep copy of the VM
|
|
69
|
+
func (v *VM) Clone() *VM {
|
|
70
|
+
return &VM{
|
|
71
|
+
ID: v.ID,
|
|
72
|
+
Name: v.Name,
|
|
73
|
+
Host: v.Host,
|
|
74
|
+
Port: v.Port,
|
|
75
|
+
Username: v.Username,
|
|
76
|
+
Auth: AuthConfig{
|
|
77
|
+
Type: v.Auth.Type,
|
|
78
|
+
KeyPath: v.Auth.KeyPath,
|
|
79
|
+
RememberPassword: v.Auth.RememberPassword,
|
|
80
|
+
},
|
|
81
|
+
LastSeen: v.LastSeen,
|
|
82
|
+
Status: v.Status,
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
package vm
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"crypto/rand"
|
|
5
|
+
"encoding/hex"
|
|
6
|
+
"fmt"
|
|
7
|
+
"os"
|
|
8
|
+
"path/filepath"
|
|
9
|
+
"sync"
|
|
10
|
+
"time"
|
|
11
|
+
|
|
12
|
+
"gopkg.in/yaml.v3"
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
// Config represents the YAML configuration file structure
|
|
16
|
+
type Config struct {
|
|
17
|
+
VMs []VM `yaml:"vms"`
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Registry manages the collection of VMs
|
|
21
|
+
type Registry struct {
|
|
22
|
+
vms map[string]*VM
|
|
23
|
+
configDir string
|
|
24
|
+
mu sync.RWMutex
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// NewRegistry creates a new VM registry
|
|
28
|
+
func NewRegistry(configDir string) (*Registry, error) {
|
|
29
|
+
if err := os.MkdirAll(configDir, 0700); err != nil {
|
|
30
|
+
return nil, fmt.Errorf("failed to create config directory: %w", err)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return &Registry{
|
|
34
|
+
vms: make(map[string]*VM),
|
|
35
|
+
configDir: configDir,
|
|
36
|
+
}, nil
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Add adds a VM to the registry
|
|
40
|
+
func (r *Registry) Add(vm *VM) error {
|
|
41
|
+
if vm == nil {
|
|
42
|
+
return fmt.Errorf("cannot add nil VM")
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Validate VM
|
|
46
|
+
if err := vm.Validate(); err != nil {
|
|
47
|
+
return fmt.Errorf("VM validation failed: %w", err)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Generate ID if not provided
|
|
51
|
+
if vm.ID == "" {
|
|
52
|
+
vm.ID = r.generateID()
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
r.mu.Lock()
|
|
56
|
+
defer r.mu.Unlock()
|
|
57
|
+
|
|
58
|
+
// Check if VM already exists
|
|
59
|
+
if _, exists := r.vms[vm.ID]; exists {
|
|
60
|
+
return ErrVMAlreadyExists
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Set initial status
|
|
64
|
+
if vm.Status == "" {
|
|
65
|
+
vm.Status = VMStatusDisconnected
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Store VM (clone to prevent external modifications)
|
|
69
|
+
r.vms[vm.ID] = vm.Clone()
|
|
70
|
+
|
|
71
|
+
return nil
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Get retrieves a VM by ID
|
|
75
|
+
func (r *Registry) Get(id string) (*VM, error) {
|
|
76
|
+
r.mu.RLock()
|
|
77
|
+
defer r.mu.RUnlock()
|
|
78
|
+
|
|
79
|
+
vm, exists := r.vms[id]
|
|
80
|
+
if !exists {
|
|
81
|
+
return nil, ErrVMNotFound
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return vm.Clone(), nil
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// List returns all VMs in the registry
|
|
88
|
+
func (r *Registry) List() []*VM {
|
|
89
|
+
r.mu.RLock()
|
|
90
|
+
defer r.mu.RUnlock()
|
|
91
|
+
|
|
92
|
+
vms := make([]*VM, 0, len(r.vms))
|
|
93
|
+
for _, vm := range r.vms {
|
|
94
|
+
vms = append(vms, vm.Clone())
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return vms
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Remove removes a VM from the registry
|
|
101
|
+
func (r *Registry) Remove(id string) error {
|
|
102
|
+
r.mu.Lock()
|
|
103
|
+
defer r.mu.Unlock()
|
|
104
|
+
|
|
105
|
+
if _, exists := r.vms[id]; !exists {
|
|
106
|
+
return ErrVMNotFound
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
delete(r.vms, id)
|
|
110
|
+
return nil
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Update updates a VM's information
|
|
114
|
+
func (r *Registry) Update(vm *VM) error {
|
|
115
|
+
if vm == nil {
|
|
116
|
+
return fmt.Errorf("cannot update with nil VM")
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if err := vm.Validate(); err != nil {
|
|
120
|
+
return fmt.Errorf("VM validation failed: %w", err)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
r.mu.Lock()
|
|
124
|
+
defer r.mu.Unlock()
|
|
125
|
+
|
|
126
|
+
if _, exists := r.vms[vm.ID]; !exists {
|
|
127
|
+
return ErrVMNotFound
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
r.vms[vm.ID] = vm.Clone()
|
|
131
|
+
return nil
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// UpdateStatus updates a VM's status
|
|
135
|
+
func (r *Registry) UpdateStatus(id string, status VMStatus) error {
|
|
136
|
+
r.mu.Lock()
|
|
137
|
+
defer r.mu.Unlock()
|
|
138
|
+
|
|
139
|
+
vm, exists := r.vms[id]
|
|
140
|
+
if !exists {
|
|
141
|
+
return ErrVMNotFound
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
vm.Status = status
|
|
145
|
+
vm.LastSeen = time.Now()
|
|
146
|
+
return nil
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Save persists the registry to the config file
|
|
150
|
+
func (r *Registry) Save() error {
|
|
151
|
+
r.mu.RLock()
|
|
152
|
+
defer r.mu.RUnlock()
|
|
153
|
+
|
|
154
|
+
config := Config{
|
|
155
|
+
VMs: make([]VM, 0, len(r.vms)),
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
for _, vm := range r.vms {
|
|
159
|
+
config.VMs = append(config.VMs, *vm)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
data, err := yaml.Marshal(&config)
|
|
163
|
+
if err != nil {
|
|
164
|
+
return fmt.Errorf("failed to marshal config: %w", err)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
configPath := filepath.Join(r.configDir, "config.yaml")
|
|
168
|
+
|
|
169
|
+
// Write with secure permissions (0600)
|
|
170
|
+
if err := os.WriteFile(configPath, data, 0600); err != nil {
|
|
171
|
+
return fmt.Errorf("failed to write config file: %w", err)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return nil
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Load reads the registry from the config file
|
|
178
|
+
func (r *Registry) Load() error {
|
|
179
|
+
configPath := filepath.Join(r.configDir, "config.yaml")
|
|
180
|
+
|
|
181
|
+
// Check if config file exists
|
|
182
|
+
if _, err := os.Stat(configPath); os.IsNotExist(err) {
|
|
183
|
+
// Config doesn't exist - this is OK for first run
|
|
184
|
+
return nil
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
data, err := os.ReadFile(configPath)
|
|
188
|
+
if err != nil {
|
|
189
|
+
return fmt.Errorf("failed to read config file: %w", err)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
var config Config
|
|
193
|
+
if err := yaml.Unmarshal(data, &config); err != nil {
|
|
194
|
+
return ErrConfigCorrupted
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
r.mu.Lock()
|
|
198
|
+
defer r.mu.Unlock()
|
|
199
|
+
|
|
200
|
+
// Clear existing VMs
|
|
201
|
+
r.vms = make(map[string]*VM)
|
|
202
|
+
|
|
203
|
+
// Load VMs from config
|
|
204
|
+
for i := range config.VMs {
|
|
205
|
+
vm := &config.VMs[i]
|
|
206
|
+
|
|
207
|
+
// Validate each VM
|
|
208
|
+
if err := vm.Validate(); err != nil {
|
|
209
|
+
// Log warning but continue loading other VMs
|
|
210
|
+
fmt.Fprintf(os.Stderr, "Warning: skipping invalid VM %s: %v\n", vm.Name, err)
|
|
211
|
+
continue
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Generate ID if missing
|
|
215
|
+
if vm.ID == "" {
|
|
216
|
+
vm.ID = r.generateID()
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Reset status to disconnected on load
|
|
220
|
+
vm.Status = VMStatusDisconnected
|
|
221
|
+
|
|
222
|
+
r.vms[vm.ID] = vm
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return nil
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Count returns the number of VMs in the registry
|
|
229
|
+
func (r *Registry) Count() int {
|
|
230
|
+
r.mu.RLock()
|
|
231
|
+
defer r.mu.RUnlock()
|
|
232
|
+
return len(r.vms)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// generateID generates a unique ID for a VM
|
|
236
|
+
func (r *Registry) generateID() string {
|
|
237
|
+
b := make([]byte, 8)
|
|
238
|
+
rand.Read(b)
|
|
239
|
+
return hex.EncodeToString(b)
|
|
240
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "heyvm",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A zero-config, interactive terminal UI to connect, manage, and transfer files to VMs over SSH",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"heyvm": "./bin/heyvm.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"postinstall": "node scripts/install.js",
|
|
11
|
+
"prepublishOnly": "npm run build",
|
|
12
|
+
"build": "npm run build:ui",
|
|
13
|
+
"build:ui": "cd ui && npm install && npm run build",
|
|
14
|
+
"version": "echo \"Version updated to $(node -p 'require(\"./package.json\").version')\"",
|
|
15
|
+
"publish:public": "npm publish --access public",
|
|
16
|
+
"dev": "make dev",
|
|
17
|
+
"test": "make test"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"ssh",
|
|
21
|
+
"vm",
|
|
22
|
+
"terminal",
|
|
23
|
+
"tui",
|
|
24
|
+
"cli",
|
|
25
|
+
"sftp",
|
|
26
|
+
"file-transfer",
|
|
27
|
+
"remote-management",
|
|
28
|
+
"devops",
|
|
29
|
+
"server-management"
|
|
30
|
+
],
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "https://github.com/adishm/heyvm.git"
|
|
34
|
+
},
|
|
35
|
+
"bugs": {
|
|
36
|
+
"url": "https://github.com/adishm/heyvm/issues"
|
|
37
|
+
},
|
|
38
|
+
"homepage": "https://github.com/adishm/heyvm#readme",
|
|
39
|
+
"author": "Adish M",
|
|
40
|
+
"license": "MIT",
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=18.0.0"
|
|
43
|
+
},
|
|
44
|
+
"files": [
|
|
45
|
+
"bin/",
|
|
46
|
+
"ui/dist/",
|
|
47
|
+
"ui/scripts/",
|
|
48
|
+
"ui/package.json",
|
|
49
|
+
"core/",
|
|
50
|
+
"scripts/",
|
|
51
|
+
"LICENSE",
|
|
52
|
+
"README.md"
|
|
53
|
+
],
|
|
54
|
+
"os": [
|
|
55
|
+
"darwin",
|
|
56
|
+
"linux",
|
|
57
|
+
"win32"
|
|
58
|
+
]
|
|
59
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Post-install script for heyvm
|
|
5
|
+
* Builds the Go backend when installed globally via npm
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { spawn } from 'child_process';
|
|
9
|
+
import { existsSync, mkdirSync } from 'fs';
|
|
10
|
+
import { dirname, join } from 'path';
|
|
11
|
+
import { fileURLToPath } from 'url';
|
|
12
|
+
|
|
13
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
14
|
+
const __dirname = dirname(__filename);
|
|
15
|
+
const rootDir = join(__dirname, '..');
|
|
16
|
+
|
|
17
|
+
console.log('🚀 Setting up heyvm...\n');
|
|
18
|
+
|
|
19
|
+
// Check if Go is installed
|
|
20
|
+
function checkGo() {
|
|
21
|
+
return new Promise((resolve) => {
|
|
22
|
+
const goVersion = spawn('go', ['version']);
|
|
23
|
+
|
|
24
|
+
goVersion.on('error', () => {
|
|
25
|
+
console.log('⚠️ Go not found - you will need Go >= 1.21 to use heyvm');
|
|
26
|
+
console.log(' Download from: https://go.dev/dl/\n');
|
|
27
|
+
resolve(false);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
goVersion.on('close', (code) => {
|
|
31
|
+
if (code === 0) {
|
|
32
|
+
console.log('✓ Go is installed');
|
|
33
|
+
resolve(true);
|
|
34
|
+
} else {
|
|
35
|
+
console.log('⚠️ Could not verify Go installation');
|
|
36
|
+
resolve(false);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Build Go backend
|
|
43
|
+
function buildCore() {
|
|
44
|
+
return new Promise((resolve, reject) => {
|
|
45
|
+
console.log('📦 Building Go backend (heyvm-core)...');
|
|
46
|
+
|
|
47
|
+
// Ensure bin directory exists
|
|
48
|
+
const binDir = join(rootDir, 'bin');
|
|
49
|
+
if (!existsSync(binDir)) {
|
|
50
|
+
mkdirSync(binDir, { recursive: true });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const coreDir = join(rootDir, 'core');
|
|
54
|
+
const buildProcess = spawn('go', ['build', '-o', '../bin/heyvm-core', './cmd/heyvm-core'], {
|
|
55
|
+
cwd: coreDir,
|
|
56
|
+
stdio: 'inherit',
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
buildProcess.on('error', (err) => {
|
|
60
|
+
reject(new Error(`Failed to build Go backend: ${err.message}`));
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
buildProcess.on('close', (code) => {
|
|
64
|
+
if (code === 0) {
|
|
65
|
+
console.log('✓ Go backend built successfully\n');
|
|
66
|
+
resolve();
|
|
67
|
+
} else {
|
|
68
|
+
reject(new Error(`Go build failed with exit code ${code}`));
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Run installation
|
|
75
|
+
async function install() {
|
|
76
|
+
try {
|
|
77
|
+
const hasGo = await checkGo();
|
|
78
|
+
|
|
79
|
+
if (!hasGo) {
|
|
80
|
+
console.log('❌ Installation incomplete: Go is required');
|
|
81
|
+
console.log('\nPlease install Go >= 1.21 from https://go.dev/dl/');
|
|
82
|
+
console.log('Then run: npm install -g heyvm\n');
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
await buildCore();
|
|
87
|
+
|
|
88
|
+
console.log('✅ heyvm installed successfully!');
|
|
89
|
+
console.log('\nRun "heyvm" to start managing your VMs\n');
|
|
90
|
+
} catch (error) {
|
|
91
|
+
console.error('\n❌ Installation failed:', error.message);
|
|
92
|
+
console.error('\nRequirements:');
|
|
93
|
+
console.error(' - Node.js >= 18.0.0');
|
|
94
|
+
console.error(' - Go >= 1.21');
|
|
95
|
+
console.error('\nFor help, visit: https://github.com/adishm/heyvm');
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
install();
|
package/ui/README.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# heyvm UI
|
|
2
|
+
|
|
3
|
+
Interactive terminal UI for heyvm, built with Ink (React for CLIs).
|
|
4
|
+
|
|
5
|
+
## Development
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Install dependencies
|
|
9
|
+
npm install
|
|
10
|
+
|
|
11
|
+
# Run in development mode
|
|
12
|
+
npm run dev
|
|
13
|
+
|
|
14
|
+
# Run with core backend
|
|
15
|
+
npm run dev:with-core
|
|
16
|
+
|
|
17
|
+
# Build for production
|
|
18
|
+
npm run build
|
|
19
|
+
|
|
20
|
+
# Run production build
|
|
21
|
+
npm start
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Architecture
|
|
25
|
+
|
|
26
|
+
- **Framework**: Ink (React for CLIs)
|
|
27
|
+
- **Language**: TypeScript
|
|
28
|
+
- **Communication**: JSON-RPC over stdio with heyvm-core
|
|
29
|
+
- **State Management**: React hooks + context
|
|
30
|
+
|
|
31
|
+
## Directory Structure
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
ui/
|
|
35
|
+
├── src/
|
|
36
|
+
│ ├── components/ # Reusable UI components
|
|
37
|
+
│ ├── screens/ # Main screen components
|
|
38
|
+
│ ├── hooks/ # Custom React hooks
|
|
39
|
+
│ ├── core/ # IPC client and types
|
|
40
|
+
│ └── index.tsx # Entry point
|
|
41
|
+
├── scripts/ # Build and utility scripts
|
|
42
|
+
└── package.json # Dependencies and scripts
|
|
43
|
+
```
|
package/ui/dist/App.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"App.d.ts","sourceRoot":"","sources":["../src/App.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAgC,MAAM,OAAO,CAAC;AAQrD,MAAM,CAAC,OAAO,UAAU,GAAG,sBAqM1B"}
|
package/ui/dist/App.js
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import React, { useState, useCallback } from 'react';
|
|
2
|
+
import { Box, Text, useApp, useInput } from 'ink';
|
|
3
|
+
import VMListScreen from './screens/VMListScreen.js';
|
|
4
|
+
import AddVMScreen from './screens/AddVMScreen.js';
|
|
5
|
+
import VMDetailScreen from './screens/VMDetailScreen.js';
|
|
6
|
+
import { ipcClient } from './core/ipc.js';
|
|
7
|
+
export default function App() {
|
|
8
|
+
// Split-pane state model
|
|
9
|
+
const [selectedVM, setSelectedVM] = useState(null);
|
|
10
|
+
const [activeTab, setActiveTab] = useState('terminal');
|
|
11
|
+
const [showAddVMModal, setShowAddVMModal] = useState(false);
|
|
12
|
+
const [activePaneSide, setActivePaneSide] = useState('left');
|
|
13
|
+
const [vmListVersion, setVMListVersion] = useState(0);
|
|
14
|
+
const { exit } = useApp();
|
|
15
|
+
// Refresh selected VM after state changes
|
|
16
|
+
const refreshSelectedVM = useCallback(async () => {
|
|
17
|
+
if (!selectedVM)
|
|
18
|
+
return;
|
|
19
|
+
try {
|
|
20
|
+
const vms = await ipcClient.listVMs();
|
|
21
|
+
const updatedVM = vms.find(v => v.id === selectedVM.id);
|
|
22
|
+
if (updatedVM) {
|
|
23
|
+
setSelectedVM(updatedVM);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
// VM was deleted, deselect it
|
|
27
|
+
setSelectedVM(null);
|
|
28
|
+
setActivePaneSide('left');
|
|
29
|
+
}
|
|
30
|
+
// Trigger VM list refresh
|
|
31
|
+
setVMListVersion(v => v + 1);
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
console.error('Failed to refresh VM:', err);
|
|
35
|
+
}
|
|
36
|
+
}, [selectedVM]);
|
|
37
|
+
// Handlers
|
|
38
|
+
const handleSelectVM = (vm) => {
|
|
39
|
+
setSelectedVM(vm);
|
|
40
|
+
setActivePaneSide('right');
|
|
41
|
+
setActiveTab('overview'); // Always default to overview
|
|
42
|
+
// No auto-connect - user must explicitly connect via 'c' key
|
|
43
|
+
};
|
|
44
|
+
const handleAddVMSubmit = async (vm, password) => {
|
|
45
|
+
try {
|
|
46
|
+
const newVM = await ipcClient.addVM(vm);
|
|
47
|
+
console.log('[App] VM added successfully:', newVM.id);
|
|
48
|
+
// Store password in keychain after VM is added (now we have ID)
|
|
49
|
+
if (password && newVM.id) {
|
|
50
|
+
console.log('[App] Storing password for VM:', newVM.id);
|
|
51
|
+
try {
|
|
52
|
+
await ipcClient.storePassword(newVM.id, password);
|
|
53
|
+
console.log('[App] Password stored successfully');
|
|
54
|
+
}
|
|
55
|
+
catch (passErr) {
|
|
56
|
+
console.error('[App] Failed to store password:', passErr);
|
|
57
|
+
// Show error to user but don't fail VM creation
|
|
58
|
+
// Password can be re-entered later
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
setShowAddVMModal(false);
|
|
62
|
+
setSelectedVM(newVM);
|
|
63
|
+
setActivePaneSide('right');
|
|
64
|
+
setActiveTab('overview');
|
|
65
|
+
// No auto-connect - user must explicitly connect via 'c' key
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
console.error('Failed to add VM:', err);
|
|
69
|
+
// Error is handled in AddVMScreen, this is just a fallback
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
const handleVMDeleted = () => {
|
|
73
|
+
setSelectedVM(null);
|
|
74
|
+
setActivePaneSide('left');
|
|
75
|
+
};
|
|
76
|
+
// Global keyboard handler
|
|
77
|
+
useInput((input, key) => {
|
|
78
|
+
// Modal takes precedence
|
|
79
|
+
if (showAddVMModal)
|
|
80
|
+
return;
|
|
81
|
+
// Ctrl+O from terminal: switch to overview tab
|
|
82
|
+
if (key.ctrl && input === 'o' && activePaneSide === 'right' && selectedVM && activeTab === 'terminal') {
|
|
83
|
+
setActiveTab('overview');
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
// Terminal has complete input priority - don't intercept ANYTHING when terminal is active
|
|
87
|
+
if (activePaneSide === 'right' && selectedVM && activeTab === 'terminal') {
|
|
88
|
+
return; // Let terminal handle all input
|
|
89
|
+
}
|
|
90
|
+
// Global quit (only from left pane with no selection)
|
|
91
|
+
if (input === 'q' && activePaneSide === 'left' && !selectedVM) {
|
|
92
|
+
exit();
|
|
93
|
+
}
|
|
94
|
+
// Tab switches panes (only when VM selected)
|
|
95
|
+
// BUT: Don't intercept Tab in Files tab (it needs Tab for local/remote switching)
|
|
96
|
+
if (key.tab && selectedVM && activeTab !== 'files') {
|
|
97
|
+
setActivePaneSide(prev => prev === 'left' ? 'right' : 'left');
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
// Escape deselects VM (from right pane)
|
|
101
|
+
if (key.escape && activePaneSide === 'right' && selectedVM) {
|
|
102
|
+
setSelectedVM(null);
|
|
103
|
+
setActivePaneSide('left');
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
// Tab switching via number keys (only from right pane, NOT in terminal)
|
|
107
|
+
if (activePaneSide === 'right' && selectedVM && activeTab !== 'terminal') {
|
|
108
|
+
if (input === '1') {
|
|
109
|
+
setActiveTab('overview');
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (input === '2') {
|
|
113
|
+
setActiveTab('terminal');
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (input === '3') {
|
|
117
|
+
setActiveTab('files');
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// Add VM (only from left pane)
|
|
122
|
+
if (input === 'a' && activePaneSide === 'left') {
|
|
123
|
+
setShowAddVMModal(true);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
return (React.createElement(Box, { flexDirection: "column", height: "100%" }, showAddVMModal ? (
|
|
128
|
+
// Modal mode: full-screen Add VM form
|
|
129
|
+
React.createElement(Box, { flexDirection: "column", padding: 2 },
|
|
130
|
+
React.createElement(AddVMScreen, { onCancel: () => setShowAddVMModal(false), onSubmit: handleAddVMSubmit }))) : (
|
|
131
|
+
// Split pane mode: VM list + detail
|
|
132
|
+
React.createElement(Box, { flexGrow: 1 },
|
|
133
|
+
React.createElement(Box, { width: "35%", flexDirection: "column", borderStyle: "single", borderColor: "gray" },
|
|
134
|
+
React.createElement(VMListScreen, { selectedVM: selectedVM, onSelectVM: handleSelectVM, isActive: activePaneSide === 'left', onVMDeleted: handleVMDeleted, vmListVersion: vmListVersion })),
|
|
135
|
+
React.createElement(Box, { width: "65%", flexDirection: "column", borderStyle: "single", borderColor: "gray" }, selectedVM ? (React.createElement(VMDetailScreen, { vm: selectedVM, activeTab: activeTab, onTabChange: setActiveTab, isActive: activePaneSide === 'right', onVMUpdated: refreshSelectedVM })) : (React.createElement(Box, { flexDirection: "column", padding: 2, justifyContent: "center", alignItems: "center", height: "100%" },
|
|
136
|
+
React.createElement(Text, { dimColor: true }, "No VM selected"),
|
|
137
|
+
React.createElement(Box, { marginTop: 1 },
|
|
138
|
+
React.createElement(Text, { dimColor: true }, "Select a VM from the list to view details")),
|
|
139
|
+
React.createElement(Box, { marginTop: 1 },
|
|
140
|
+
React.createElement(Text, { dimColor: true }, "Press 'a' to add a new VM")))))))));
|
|
141
|
+
}
|
|
142
|
+
//# sourceMappingURL=App.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"App.js","sourceRoot":"","sources":["../src/App.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
interface ConfirmDialogProps {
|
|
3
|
+
message: string;
|
|
4
|
+
onConfirm: () => void;
|
|
5
|
+
onCancel: () => void;
|
|
6
|
+
}
|
|
7
|
+
export default function ConfirmDialog({ message, onConfirm, onCancel }: ConfirmDialogProps): React.JSX.Element;
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=ConfirmDialog.d.ts.map
|