vscode extensions

This commit is contained in:
Andy Dixon
2026-06-23 19:12:24 +01:00
parent 89e0af3817
commit 0f0b18c124
18 changed files with 4036 additions and 16 deletions
+85 -2
View File
@@ -61,6 +61,7 @@ type Service struct {
auditLog *audit.Logger
prefs *preferences.Manager
ctx context.Context
eventSink func(string, interface{})
// secretChans maps request IDs to channels awaiting user secret input.
secretMu sync.Mutex
@@ -107,6 +108,12 @@ func (s *Service) SetContext(ctx context.Context) {
s.RegisterSecretResponseListener()
}
// SetEventSink registers an alternate UI event sink for non-Wails hosts such
// as the VS Code bridge.
func (s *Service) SetEventSink(sink func(string, interface{})) {
s.eventSink = sink
}
// ---------------------------------------------------------------------------
// Profile operations
// ---------------------------------------------------------------------------
@@ -1026,6 +1033,26 @@ func (s *Service) DownloadGitHubRepoArchive(sessionID string, fullName string, r
return path, nil
}
// DownloadGitHubRepoArchiveToPath downloads a repository zip archive to an
// explicit local destination. Non-Wails hosts provide the save path through
// their own file picker.
func (s *Service) DownloadGitHubRepoArchiveToPath(sessionID string, fullName string, ref string, destPath string) (string, error) {
if strings.TrimSpace(fullName) == "" {
return "", fmt.Errorf("repository name cannot be empty")
}
destPath = strings.TrimSpace(destPath)
if destPath == "" {
return "", fmt.Errorf("destination path cannot be empty")
}
if filepath.Ext(destPath) != ".zip" {
destPath += ".zip"
}
if err := s.sessions.DownloadGitHubArchive(sessionID, fullName, ref, destPath); err != nil {
return "", err
}
return destPath, nil
}
// ---------------------------------------------------------------------------
// VNC
// ---------------------------------------------------------------------------
@@ -1410,6 +1437,19 @@ func (s *Service) SFTPDownload(sessionID string, remotePath string) error {
return s.sessions.SFTPDownload(sessionID, remotePath, localPath)
}
// SFTPDownloadToPath downloads a remote file to an explicit local path. This
// is used by non-Wails hosts, where the UI owns the save dialog.
func (s *Service) SFTPDownloadToPath(sessionID string, remotePath string, localPath string) error {
if strings.TrimSpace(remotePath) == "" {
return fmt.Errorf("remote path cannot be empty")
}
localPath = strings.TrimSpace(localPath)
if localPath == "" {
return fmt.Errorf("local path cannot be empty")
}
return s.sessions.SFTPDownload(sessionID, remotePath, localPath)
}
// SFTPUpload uploads a local file to a remote directory. Opens a native file
// dialog to select the local file.
func (s *Service) SFTPUpload(sessionID string, remotePath string) error {
@@ -1431,6 +1471,29 @@ func (s *Service) SFTPUpload(sessionID string, remotePath string) error {
return s.sessions.SFTPUpload(sessionID, localPath, destPath)
}
// SFTPUploadPath uploads an explicit local file path to a remote directory.
// The remote filename is derived from the local basename and sanitised through
// the same upload-name validation used by browser uploads.
func (s *Service) SFTPUploadPath(sessionID string, remoteDir string, localPath string) error {
localPath = strings.TrimSpace(localPath)
if localPath == "" {
return fmt.Errorf("local path cannot be empty")
}
info, err := os.Stat(localPath)
if err != nil {
return fmt.Errorf("reading local file metadata: %w", err)
}
if info.IsDir() {
return fmt.Errorf("local path is a directory")
}
safeName, err := safeSFTPUploadName(filepath.Base(localPath))
if err != nil {
return err
}
destPath := path.Join(remoteDir, safeName)
return s.sessions.SFTPUpload(sessionID, localPath, destPath)
}
// SFTPUploadData uploads raw file data to a remote directory.
// Data is expected as base64-encoded bytes from the frontend.
func (s *Service) SFTPUploadData(sessionID string, remoteDir string, fileName string, base64Data string) error {
@@ -1676,7 +1739,7 @@ func (s *Service) TestConnection(host string, port int) (map[string]interface{},
// This bridges the synchronous Go SSH auth callback with the asynchronous
// frontend prompt UI.
func (s *Service) promptSecret(prompt string) (string, error) {
if s.ctx == nil {
if s.ctx == nil && s.eventSink == nil {
return "", fmt.Errorf("application context not available")
}
@@ -1693,7 +1756,7 @@ func (s *Service) promptSecret(prompt string) (string, error) {
s.secretMu.Unlock()
// Emit the prompt event to the frontend.
wailsRuntime.EventsEmit(s.ctx, "session:secret:prompt", map[string]interface{}{
s.emitEvent("session:secret:prompt", map[string]interface{}{
"requestID": requestID,
"message": prompt,
"title": "Credentials Required",
@@ -1751,6 +1814,26 @@ func (s *Service) RegisterSecretResponseListener() {
})
}
// RespondSecret supplies a credential prompt response for non-Wails hosts.
func (s *Service) RespondSecret(requestID string, value string, cancelled bool) {
s.secretMu.Lock()
ch, exists := s.secretChans[requestID]
s.secretMu.Unlock()
if exists {
ch <- secretResponse{value: value, cancelled: cancelled}
}
}
func (s *Service) emitEvent(eventName string, data interface{}) {
if s.eventSink != nil {
s.eventSink(eventName, data)
}
if s.ctx != nil {
wailsRuntime.EventsEmit(s.ctx, eventName, data)
}
}
// derefProfiles converts a slice of profile pointers to values.
func derefProfiles(ptrs []*models.Profile) []models.Profile {
result := make([]models.Profile, len(ptrs))
+17
View File
@@ -218,6 +218,23 @@ func (s *Service) LeviathanSaveWorking() (string, error) {
return path, writeLeviathanDocumentFile(path, doc)
}
// LeviathanSaveWorkingToPath writes the patched bytes to an explicit path.
// This is used by non-Wails hosts, where file picking is owned by the host UI.
func (s *Service) LeviathanSaveWorkingToPath(path string) (string, error) {
engine := s.leviathanEngine()
engine.mu.Lock()
if engine.doc == nil {
engine.mu.Unlock()
return "", errors.New("open a file first")
}
doc := engine.doc
engine.mu.Unlock()
if strings.TrimSpace(path) == "" {
return "", errors.New("save path is required")
}
return path, writeLeviathanDocumentFile(path, doc)
}
// LeviathanExportJSON writes a tool result to disk.
func (s *Service) LeviathanExportJSON(name string, value map[string]any) (string, error) {
if s.ctx == nil {