diff --git a/backend/src/terminal/sftp.gateway.ts b/backend/src/terminal/sftp.gateway.ts index 2dfc657..745b4a9 100644 --- a/backend/src/terminal/sftp.gateway.ts +++ b/backend/src/terminal/sftp.gateway.ts @@ -59,26 +59,44 @@ export class SftpGateway { switch (msg.type) { case 'list': { - this.logger.log(`[SFTP] readdir starting for path: "${msg.path}"`); - try { - sftp.readdir(msg.path, (err: any, list: any[]) => { - this.logger.log(`[SFTP] readdir callback fired, err=${err?.message || 'null'}, entries=${list?.length || 0}`); - if (err) return this.send(client, { type: 'error', message: err.message }); - const entries = list.map((f: any) => ({ - name: f.filename, - path: `${msg.path === '/' ? '' : msg.path}/${f.filename}`, - size: f.attrs.size, - isDirectory: (f.attrs.mode & 0o40000) !== 0, - permissions: (f.attrs.mode & 0o7777).toString(8), - modified: new Date(f.attrs.mtime * 1000).toISOString(), - })); - this.logger.log(`[SFTP] Sending list response with ${entries.length} entries, client.readyState=${client.readyState}`); - this.send(client, { type: 'list', path: msg.path, entries }); - }); - } catch (syncErr: any) { - this.logger.error(`[SFTP] readdir threw synchronously: ${syncErr.message}`); - this.send(client, { type: 'error', message: syncErr.message }); - } + // Resolve '~' to the user's home directory via SFTP realpath('.') + const resolvePath = (path: string, cb: (resolved: string) => void) => { + if (path === '~') { + sftp.realpath('.', (err: any, absPath: string) => { + if (err) { + this.logger.warn(`[SFTP] realpath('.') failed, falling back to /: ${err.message}`); + cb('/'); + } else { + cb(absPath); + } + }); + } else { + cb(path); + } + }; + + resolvePath(msg.path, (resolvedPath) => { + this.logger.log(`[SFTP] readdir starting for path: "${resolvedPath}"`); + try { + sftp.readdir(resolvedPath, (err: any, list: any[]) => { + this.logger.log(`[SFTP] readdir callback fired, err=${err?.message || 'null'}, entries=${list?.length || 0}`); + if (err) return this.send(client, { type: 'error', message: err.message }); + const entries = list.map((f: any) => ({ + name: f.filename, + path: `${resolvedPath === '/' ? '' : resolvedPath}/${f.filename}`, + size: f.attrs.size, + isDirectory: (f.attrs.mode & 0o40000) !== 0, + permissions: (f.attrs.mode & 0o7777).toString(8), + modified: new Date(f.attrs.mtime * 1000).toISOString(), + })); + this.logger.log(`[SFTP] Sending list response with ${entries.length} entries, client.readyState=${client.readyState}`); + this.send(client, { type: 'list', path: resolvedPath, entries }); + }); + } catch (syncErr: any) { + this.logger.error(`[SFTP] readdir threw synchronously: ${syncErr.message}`); + this.send(client, { type: 'error', message: syncErr.message }); + } + }); break; } case 'read': { diff --git a/backend/src/terminal/ssh-connection.service.ts b/backend/src/terminal/ssh-connection.service.ts index 63956a7..359a6dd 100644 --- a/backend/src/terminal/ssh-connection.service.ts +++ b/backend/src/terminal/ssh-connection.service.ts @@ -49,6 +49,20 @@ export class SshConnectionService { this.sessions.set(sessionId, session); stream.on('data', (data: Buffer) => onData(data.toString('utf-8'))); + + // Shell integration: inject PROMPT_COMMAND (bash) / precmd (zsh) to emit + // OSC 7 escape sequences reporting the current working directory on every prompt. + // Leading space prevents the command from being saved to shell history. + // The frontend captures OSC 7 via xterm.js and syncs the SFTP sidebar. + const shellIntegration = + ` if [ -n "$ZSH_VERSION" ]; then` + + ` __wraith_cwd(){ printf '\\e]7;file://%s%s\\a' "$HOST" "$PWD"; };` + + ` precmd_functions+=(__wraith_cwd);` + + ` elif [ -n "$BASH_VERSION" ]; then` + + ` PROMPT_COMMAND='printf "\\033]7;file://%s%s\\a" "$HOSTNAME" "$PWD"';` + + ` fi\n`; + stream.write(shellIntegration); + stream.on('close', () => { this.disconnect(sessionId); onClose('Session ended'); diff --git a/frontend/components/sftp/SftpSidebar.vue b/frontend/components/sftp/SftpSidebar.vue index e785313..c22fd0b 100644 --- a/frontend/components/sftp/SftpSidebar.vue +++ b/frontend/components/sftp/SftpSidebar.vue @@ -1,17 +1,32 @@