@prmichaelsen/acp-mcp 0.5.0 → 0.6.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/CHANGELOG.md +35 -0
- package/README.md +5 -3
- package/agent/progress.yaml +62 -13
- package/agent/tasks/task-5-fix-incomplete-directory-listings.md +170 -0
- package/dist/server-factory.js +169 -22
- package/dist/server-factory.js.map +4 -4
- package/dist/server.js +169 -22
- package/dist/server.js.map +4 -4
- package/dist/types/file-entry.d.ts +88 -0
- package/dist/utils/ssh-connection.d.ts +13 -5
- package/package.json +1 -1
- package/src/tools/acp-remote-list-files.ts +27 -21
- package/src/types/file-entry.ts +123 -0
- package/src/utils/ssh-connection.ts +123 -5
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../src/server-factory.ts", "../src/tools/acp-remote-list-files.ts", "../src/utils/logger.ts", "../src/tools/acp-remote-execute-command.ts", "../src/tools/acp-remote-read-file.ts", "../src/tools/acp-remote-write-file.ts", "../src/utils/ssh-connection.ts"],
|
|
4
|
-
"sourcesContent": ["import { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport {\n CallToolRequestSchema,\n ListToolsRequestSchema,\n} from '@modelcontextprotocol/sdk/types.js';\nimport { config } from './config.js';\nimport { acpRemoteListFilesTool, handleAcpRemoteListFiles } from './tools/acp-remote-list-files.js';\nimport { acpRemoteExecuteCommandTool, handleAcpRemoteExecuteCommand } from './tools/acp-remote-execute-command.js';\nimport { acpRemoteReadFileTool, handleAcpRemoteReadFile } from './tools/acp-remote-read-file.js';\nimport { acpRemoteWriteFileTool, handleAcpRemoteWriteFile } from './tools/acp-remote-write-file.js';\nimport { ServerConfig } from './types/ssh-config.js';\nimport { SSHConnectionManager } from './utils/ssh-connection.js';\nimport { logger } from './utils/logger.js';\n\n/**\n * Create an MCP server instance for a specific user with SSH configuration\n * This factory function is used by mcp-auth for multi-tenant support\n * \n * @param serverConfig - Configuration including userId and SSH credentials\n * @returns Configured MCP Server instance\n */\nexport async function createServer(serverConfig: ServerConfig): Promise<Server> {\n logger.info('Creating server instance', { userId: serverConfig.userId });\n logger.debug('SSH configuration', {\n host: serverConfig.ssh.host,\n port: serverConfig.ssh.port,\n username: serverConfig.ssh.username,\n });\n \n // Create SSH connection manager\n const sshConnection = new SSHConnectionManager(serverConfig.ssh);\n \n // Connect to remote server\n await sshConnection.connect();\n \n logger.info('Server created successfully', { userId: serverConfig.userId });\n\n const server = new Server(\n {\n name: 'acp-mcp',\n version: '0.1.0',\n },\n {\n capabilities: {\n tools: {},\n },\n }\n );\n\n // Register tools with SSH connection context\n server.setRequestHandler(ListToolsRequestSchema, async () => {\n logger.debug('Tool discovery requested', { userId: serverConfig.userId });\n const tools = [acpRemoteListFilesTool, acpRemoteExecuteCommandTool, acpRemoteReadFileTool, acpRemoteWriteFileTool];\n logger.debug(`Returning ${tools.length} tools`, { tools: tools.map(t => t.name), userId: serverConfig.userId });\n return { tools };\n });\n\n server.setRequestHandler(CallToolRequestSchema, async (request) => {\n const startTime = Date.now();\n logger.toolInvoked(request.params.name, request.params.arguments, serverConfig.userId);\n \n try {\n let result;\n \n if (request.params.name === 'acp_remote_list_files') {\n // Pass SSH connection to handler for remote operations\n result = await handleAcpRemoteListFiles(request.params.arguments, sshConnection);\n } else if (request.params.name === 'acp_remote_execute_command') {\n result = await handleAcpRemoteExecuteCommand(request.params.arguments, sshConnection);\n } else if (request.params.name === 'acp_remote_read_file') {\n result = await handleAcpRemoteReadFile(request.params.arguments, sshConnection);\n } else if (request.params.name === 'acp_remote_write_file') {\n result = await handleAcpRemoteWriteFile(request.params.arguments, sshConnection);\n } else {\n throw new Error(`Unknown tool: ${request.params.name}`);\n }\n \n const duration = Date.now() - startTime;\n const resultSize = JSON.stringify(result).length;\n logger.toolCompleted(request.params.name, duration, resultSize);\n \n return result;\n } catch (error) {\n logger.toolFailed(request.params.name, error as Error, request.params.arguments);\n throw error;\n }\n });\n\n // Handle server shutdown to cleanup SSH connection\n process.on('SIGINT', () => {\n logger.info('Received SIGINT, shutting down', { userId: serverConfig.userId });\n sshConnection.disconnect();\n process.exit(0);\n });\n\n process.on('SIGTERM', () => {\n logger.info('Received SIGTERM, shutting down', { userId: serverConfig.userId });\n sshConnection.disconnect();\n process.exit(0);\n });\n\n return server;\n}\n", "import { Tool } from '@modelcontextprotocol/sdk/types.js';\nimport { SSHConnectionManager } from '../utils/ssh-connection.js';\n\nexport const acpRemoteListFilesTool: Tool = {\n name: 'acp_remote_list_files',\n description: 'List files and directories in a specified path on the remote machine via SSH',\n inputSchema: {\n type: 'object',\n properties: {\n path: {\n type: 'string',\n description: 'The directory path to list files from',\n },\n recursive: {\n type: 'boolean',\n description: 'Whether to list files recursively',\n default: false,\n },\n },\n required: ['path'],\n },\n};\n\ninterface ListFilesArgs {\n path: string;\n recursive?: boolean;\n}\n\n/**\n * Handle the acp_remote_list_files tool invocation\n * Lists files and directories at the specified path on the remote machine via SSH\n * \n * @param args - Tool arguments containing path and recursive flag\n * @param sshConnection - SSH connection manager for remote operations\n */\nexport async function handleAcpRemoteListFiles(\n args: any,\n sshConnection: SSHConnectionManager\n): Promise<{ content: Array<{ type: string; text: string }> }> {\n const { path, recursive = false } = args as ListFilesArgs;\n\n try {\n const files = await listRemoteFiles(sshConnection, path, recursive);\n \n return {\n content: [\n {\n type: 'text',\n text: files.join('\\n'),\n },\n ],\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n return {\n content: [\n {\n type: 'text',\n text: `Error listing remote files: ${errorMessage}`,\n },\n ],\n };\n }\n}\n\n/**\n * Recursively list files in a remote directory via SSH\n * Returns absolute paths for compatibility with acp_remote_read_file\n */\nasync function listRemoteFiles(\n ssh: SSHConnectionManager,\n dirPath: string,\n recursive: boolean\n): Promise<string[]> {\n const entries = await ssh.listFiles(dirPath);\n const files: string[] = [];\n\n for (const entry of entries) {\n // Construct absolute path by combining dirPath with entry name\n const fullPath = `${dirPath}/${entry.name}`.replace(/\\/+/g, '/');\n \n if (entry.isDirectory) {\n files.push(`${fullPath}/`); // Return absolute path with trailing slash\n if (recursive) {\n const subFiles = await listRemoteFiles(ssh, fullPath, recursive);\n files.push(...subFiles); // Sub-files already have absolute paths\n }\n } else {\n files.push(fullPath); // Return absolute path\n }\n }\n\n return files.sort();\n}\n", "/**\n * Logger utility for ACP MCP Server\n * Provides structured logging with configurable log levels\n */\n\nexport type LogLevel = 'error' | 'warn' | 'info' | 'debug' | 'trace';\n\nconst LOG_LEVELS: Record<LogLevel, number> = {\n error: 0,\n warn: 1,\n info: 2,\n debug: 3,\n trace: 4,\n};\n\nclass Logger {\n private level: LogLevel;\n private enabled: boolean;\n\n constructor() {\n // Read configuration from environment variables\n this.level = (process.env.ACP_MCP_LOG_LEVEL as LogLevel) || 'info';\n this.enabled = process.env.ACP_MCP_DEBUG === 'true' || process.env.NODE_ENV === 'development';\n }\n\n private shouldLog(level: LogLevel): boolean {\n if (!this.enabled && level !== 'error' && level !== 'warn') {\n return false;\n }\n return LOG_LEVELS[level] <= LOG_LEVELS[this.level];\n }\n\n private formatMessage(level: LogLevel, message: string, data?: any): string {\n const timestamp = new Date().toISOString();\n const prefix = `[${timestamp}] [${level.toUpperCase()}]`;\n \n if (data !== undefined) {\n const dataStr = typeof data === 'object' ? JSON.stringify(data, null, 2) : String(data);\n return `${prefix} ${message}\\n${dataStr}`;\n }\n \n return `${prefix} ${message}`;\n }\n\n error(message: string, data?: any): void {\n if (this.shouldLog('error')) {\n console.error(this.formatMessage('error', message, data));\n }\n }\n\n warn(message: string, data?: any): void {\n if (this.shouldLog('warn')) {\n console.error(this.formatMessage('warn', message, data));\n }\n }\n\n info(message: string, data?: any): void {\n if (this.shouldLog('info')) {\n console.error(this.formatMessage('info', message, data));\n }\n }\n\n debug(message: string, data?: any): void {\n if (this.shouldLog('debug')) {\n console.error(this.formatMessage('debug', message, data));\n }\n }\n\n trace(message: string, data?: any): void {\n if (this.shouldLog('trace')) {\n console.error(this.formatMessage('trace', message, data));\n }\n }\n\n /**\n * Log tool invocation with parameters\n */\n toolInvoked(toolName: string, params: any, userId?: string): void {\n this.info(`Tool invoked: ${toolName}`);\n this.debug('Tool parameters', { tool: toolName, params, userId });\n }\n\n /**\n * Log tool completion with result summary\n */\n toolCompleted(toolName: string, duration: number, resultSize?: number): void {\n this.info(`Tool completed: ${toolName}`);\n this.debug('Tool performance', { tool: toolName, duration: `${duration}ms`, resultSize });\n }\n\n /**\n * Log tool failure with error details\n */\n toolFailed(toolName: string, error: Error, params?: any): void {\n this.error(`Tool execution failed: ${toolName}`, {\n tool: toolName,\n error: error.message,\n stack: error.stack,\n params,\n });\n }\n\n /**\n * Log SSH command execution\n */\n sshCommand(command: string, cwd?: string, timeout?: number): void {\n this.debug('Executing SSH command', { command, cwd, timeout });\n }\n\n /**\n * Log SSH command result\n */\n sshCommandResult(exitCode: number, duration: number, stdoutSize: number, stderrSize: number): void {\n this.debug('SSH command completed', {\n exitCode,\n duration: `${duration}ms`,\n stdout: `${stdoutSize} bytes`,\n stderr: `${stderrSize} bytes`,\n });\n }\n\n /**\n * Log file operation\n */\n fileOperation(operation: string, path: string, details?: any): void {\n this.info(`File operation: ${operation}`, { path, ...details });\n }\n}\n\n// Export singleton instance\nexport const logger = new Logger();\n", "import { Tool } from '@modelcontextprotocol/sdk/types.js';\nimport { SSHConnectionManager } from '../utils/ssh-connection.js';\nimport { logger } from '../utils/logger.js';\n\nexport const acpRemoteExecuteCommandTool: Tool = {\n name: 'acp_remote_execute_command',\n description: 'Execute a shell command on the remote machine via SSH',\n inputSchema: {\n type: 'object',\n properties: {\n command: {\n type: 'string',\n description: 'Shell command to execute',\n },\n cwd: {\n type: 'string',\n description: 'Working directory for command execution (optional)',\n },\n timeout: {\n type: 'number',\n description: 'Timeout in seconds (default: 30)',\n default: 30,\n },\n },\n required: ['command'],\n },\n};\n\ninterface ExecuteCommandArgs {\n command: string;\n cwd?: string;\n timeout?: number;\n}\n\ninterface ExecuteCommandResult {\n stdout: string;\n stderr: string;\n exitCode: number;\n timedOut: boolean;\n}\n\n/**\n * Handle the acp_remote_execute_command tool invocation\n * Executes a shell command on the remote machine via SSH\n * \n * @param args - Tool arguments containing command, cwd, and timeout\n * @param sshConnection - SSH connection manager for remote operations\n */\nexport async function handleAcpRemoteExecuteCommand(\n args: any,\n sshConnection: SSHConnectionManager\n): Promise<{ content: Array<{ type: string; text: string }> }> {\n const { command, cwd, timeout = 30 } = args as ExecuteCommandArgs;\n\n logger.debug('Executing remote command', { command, cwd, timeout });\n\n try {\n // Build command with working directory if specified\n const fullCommand = cwd ? `cd ${cwd} && ${command}` : command;\n \n const result = await sshConnection.execWithTimeout(fullCommand, timeout);\n \n logger.debug('Command execution result', {\n exitCode: result.exitCode,\n timedOut: result.timedOut,\n stdoutLength: result.stdout.length,\n stderrLength: result.stderr.length,\n });\n \n // Format output as JSON for structured response\n const output: ExecuteCommandResult = {\n stdout: result.stdout,\n stderr: result.stderr,\n exitCode: result.exitCode,\n timedOut: result.timedOut,\n };\n \n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(output, null, 2),\n },\n ],\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n logger.error('Command execution error', { command, error: errorMessage });\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify({\n stdout: '',\n stderr: errorMessage,\n exitCode: 1,\n timedOut: false,\n }, null, 2),\n },\n ],\n };\n }\n}\n", "import { Tool } from '@modelcontextprotocol/sdk/types.js';\nimport { SSHConnectionManager } from '../utils/ssh-connection.js';\nimport { logger } from '../utils/logger.js';\n\nexport const acpRemoteReadFileTool: Tool = {\n name: 'acp_remote_read_file',\n description: 'Read file contents from the remote machine via SSH',\n inputSchema: {\n type: 'object',\n properties: {\n path: {\n type: 'string',\n description: 'Absolute path to file',\n },\n encoding: {\n type: 'string',\n description: 'File encoding (default: utf-8)',\n default: 'utf-8',\n enum: ['utf-8', 'ascii', 'base64'],\n },\n maxSize: {\n type: 'number',\n description: 'Max file size in bytes (default: 1MB)',\n default: 1048576,\n },\n },\n required: ['path'],\n },\n};\n\ninterface ReadFileArgs {\n path: string;\n encoding?: string;\n maxSize?: number;\n}\n\ninterface ReadFileResult {\n content: string;\n size: number;\n encoding: string;\n}\n\n/**\n * Handle the acp_remote_read_file tool invocation\n * Reads file contents from the remote machine via SSH\n * \n * @param args - Tool arguments containing path, encoding, and maxSize\n * @param sshConnection - SSH connection manager for remote operations\n */\nexport async function handleAcpRemoteReadFile(\n args: any,\n sshConnection: SSHConnectionManager\n): Promise<{ content: Array<{ type: string; text: string }> }> {\n const { path, encoding = 'utf-8', maxSize = 1048576 } = args as ReadFileArgs;\n\n logger.debug('Reading remote file', { path, encoding, maxSize });\n\n try {\n const result = await sshConnection.readFile(path, encoding, maxSize);\n \n logger.debug('File read successful', { path, size: result.size });\n \n const output: ReadFileResult = {\n content: result.content,\n size: result.size,\n encoding: result.encoding,\n };\n \n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(output, null, 2),\n },\n ],\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n logger.error('File read error', { path, error: errorMessage });\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify({\n error: errorMessage,\n content: '',\n size: 0,\n encoding,\n }, null, 2),\n },\n ],\n };\n }\n}\n", "import { Tool } from '@modelcontextprotocol/sdk/types.js';\nimport { SSHConnectionManager } from '../utils/ssh-connection.js';\nimport { logger } from '../utils/logger.js';\n\nexport const acpRemoteWriteFileTool: Tool = {\n name: 'acp_remote_write_file',\n description: 'Write file contents to the remote machine via SSH',\n inputSchema: {\n type: 'object',\n properties: {\n path: {\n type: 'string',\n description: 'Absolute path to file',\n },\n content: {\n type: 'string',\n description: 'File contents to write',\n },\n encoding: {\n type: 'string',\n description: 'File encoding (default: utf-8)',\n default: 'utf-8',\n },\n createDirs: {\n type: 'boolean',\n description: 'Create parent directories if they don\\'t exist (default: false)',\n default: false,\n },\n backup: {\n type: 'boolean',\n description: 'Backup existing file before overwriting (default: false)',\n default: false,\n },\n },\n required: ['path', 'content'],\n },\n};\n\ninterface WriteFileArgs {\n path: string;\n content: string;\n encoding?: string;\n createDirs?: boolean;\n backup?: boolean;\n}\n\ninterface WriteFileResult {\n success: boolean;\n bytesWritten: number;\n backupPath?: string;\n}\n\n/**\n * Handle the acp_remote_write_file tool invocation\n * Writes file contents to the remote machine via SSH\n * \n * @param args - Tool arguments containing path, content, and options\n * @param sshConnection - SSH connection manager for remote operations\n */\nexport async function handleAcpRemoteWriteFile(\n args: any,\n sshConnection: SSHConnectionManager\n): Promise<{ content: Array<{ type: string; text: string }> }> {\n const { path, content, encoding = 'utf-8', createDirs = false, backup = false } = args as WriteFileArgs;\n\n logger.debug('Writing remote file', { path, contentSize: content.length, encoding, createDirs, backup });\n\n try {\n const result = await sshConnection.writeFile(path, content, {\n encoding,\n createDirs,\n backup,\n });\n \n logger.debug('File write successful', { path, bytesWritten: result.bytesWritten, backupPath: result.backupPath });\n \n const output: WriteFileResult = {\n success: result.success,\n bytesWritten: result.bytesWritten,\n backupPath: result.backupPath,\n };\n \n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(output, null, 2),\n },\n ],\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n logger.error('File write error', { path, error: errorMessage });\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify({\n success: false,\n bytesWritten: 0,\n error: errorMessage,\n }, null, 2),\n },\n ],\n };\n }\n}\n", "import { Client, SFTPWrapper } from 'ssh2';\nimport { SSHConfig } from '../types/ssh-config.js';\nimport { logger } from './logger.js';\n\n/**\n * SSH Connection Manager\n * Manages SSH connections and provides SFTP access for remote file operations\n */\nexport class SSHConnectionManager {\n private client: Client;\n private config: SSHConfig;\n private connected: boolean = false;\n\n constructor(config: SSHConfig) {\n this.config = config;\n this.client = new Client();\n }\n\n /**\n * Connect to the remote SSH server\n */\n async connect(): Promise<void> {\n if (this.connected) {\n logger.debug('SSH connection already established');\n return;\n }\n\n logger.info('Connecting to SSH server', {\n host: this.config.host,\n port: this.config.port || 22,\n username: this.config.username,\n });\n\n return new Promise((resolve, reject) => {\n this.client\n .on('ready', () => {\n this.connected = true;\n logger.info('SSH connection established', {\n host: this.config.host,\n username: this.config.username,\n });\n resolve();\n })\n .on('error', (err) => {\n logger.error('SSH connection failed', {\n host: this.config.host,\n error: err.message,\n });\n reject(err);\n })\n .connect({\n host: this.config.host,\n port: this.config.port || 22,\n username: this.config.username,\n privateKey: this.config.privateKey,\n });\n });\n }\n\n /**\n * Execute a command on the remote server\n */\n async exec(command: string): Promise<string> {\n if (!this.connected) {\n await this.connect();\n }\n\n return new Promise((resolve, reject) => {\n this.client.exec(command, (err, stream) => {\n if (err) {\n reject(err);\n return;\n }\n\n let stdout = '';\n let stderr = '';\n\n stream\n .on('close', (code: number) => {\n if (code !== 0) {\n reject(new Error(`Command failed with code ${code}: ${stderr}`));\n } else {\n resolve(stdout);\n }\n })\n .on('data', (data: Buffer) => {\n stdout += data.toString();\n })\n .stderr.on('data', (data: Buffer) => {\n stderr += data.toString();\n });\n });\n });\n }\n\n /**\n * Execute a command on the remote server with timeout support\n */\n async execWithTimeout(\n command: string,\n timeoutSeconds: number = 30\n ): Promise<{ stdout: string; stderr: string; exitCode: number; timedOut: boolean }> {\n if (!this.connected) {\n await this.connect();\n }\n\n const startTime = Date.now();\n logger.sshCommand(command, undefined, timeoutSeconds);\n\n const execPromise = new Promise<{ stdout: string; stderr: string; exitCode: number }>((resolve, reject) => {\n this.client.exec(command, (err, stream) => {\n if (err) {\n reject(err);\n return;\n }\n\n let stdout = '';\n let stderr = '';\n\n stream\n .on('close', (code: number) => {\n const duration = Date.now() - startTime;\n logger.sshCommandResult(code, duration, stdout.length, stderr.length);\n resolve({ stdout, stderr, exitCode: code });\n })\n .on('data', (data: Buffer) => {\n stdout += data.toString();\n })\n .stderr.on('data', (data: Buffer) => {\n stderr += data.toString();\n });\n });\n });\n\n const timeoutPromise = new Promise<never>((_, reject) => {\n setTimeout(() => {\n reject(new Error('Command execution timed out'));\n }, timeoutSeconds * 1000);\n });\n\n try {\n const result = await Promise.race([execPromise, timeoutPromise]);\n return { ...result, timedOut: false };\n } catch (error) {\n if (error instanceof Error && error.message === 'Command execution timed out') {\n logger.warn('SSH command timed out', { command, timeout: timeoutSeconds });\n return {\n stdout: '',\n stderr: 'Command execution timed out',\n exitCode: 124,\n timedOut: true,\n };\n }\n logger.error('SSH command execution failed', {\n command,\n error: error instanceof Error ? error.message : String(error),\n });\n throw error;\n }\n }\n\n /**\n * Get SFTP wrapper for file operations\n */\n async getSFTP(): Promise<SFTPWrapper> {\n if (!this.connected) {\n await this.connect();\n }\n\n return new Promise((resolve, reject) => {\n this.client.sftp((err, sftp) => {\n if (err) {\n reject(err);\n } else {\n resolve(sftp);\n }\n });\n });\n }\n\n /**\n * List files in a directory using SFTP\n */\n async listFiles(path: string): Promise<Array<{ name: string; isDirectory: boolean }>> {\n const sftp = await this.getSFTP();\n\n return new Promise((resolve, reject) => {\n sftp.readdir(path, (err, list) => {\n if (err) {\n reject(err);\n return;\n }\n\n const files = list.map((item) => ({\n name: item.filename,\n isDirectory: item.attrs.isDirectory(),\n }));\n\n resolve(files);\n });\n });\n }\n\n /**\n * Read file contents from remote machine\n */\n async readFile(\n path: string,\n encoding: string = 'utf-8',\n maxSize: number = 1048576\n ): Promise<{ content: string; size: number; encoding: string }> {\n const startTime = Date.now();\n logger.fileOperation('read', path, { encoding, maxSize });\n \n const sftp = await this.getSFTP();\n\n return new Promise((resolve, reject) => {\n // First, get file stats to check size\n sftp.stat(path, (err, stats) => {\n if (err) {\n logger.error('File stat failed', { path, error: err.message });\n reject(new Error(`File not found or inaccessible: ${path}`));\n return;\n }\n\n logger.debug('File stat retrieved', { path, size: stats.size });\n\n if (stats.size > maxSize) {\n logger.warn('File too large', { path, size: stats.size, maxSize });\n reject(new Error(`File too large: ${stats.size} bytes (max: ${maxSize} bytes)`));\n return;\n }\n\n // Read file contents\n sftp.readFile(path, { encoding: encoding as BufferEncoding }, (err, data) => {\n if (err) {\n logger.error('File read failed', { path, error: err.message });\n reject(new Error(`Failed to read file: ${err.message}`));\n return;\n }\n\n const duration = Date.now() - startTime;\n logger.debug('File read completed', { path, size: stats.size, duration: `${duration}ms` });\n\n resolve({\n content: data.toString(),\n size: stats.size,\n encoding,\n });\n });\n });\n });\n }\n\n /**\n * Write file contents to remote machine\n */\n async writeFile(\n path: string,\n content: string,\n options: {\n encoding?: string;\n createDirs?: boolean;\n backup?: boolean;\n } = {}\n ): Promise<{ success: boolean; bytesWritten: number; backupPath?: string }> {\n const { encoding = 'utf-8', createDirs = false, backup = false } = options;\n const startTime = Date.now();\n \n logger.fileOperation('write', path, {\n contentSize: content.length,\n encoding,\n createDirs,\n backup,\n });\n \n const sftp = await this.getSFTP();\n\n return new Promise((resolve, reject) => {\n const writeOperation = () => {\n // Create backup if requested\n if (backup) {\n const backupPath = `${path}.backup`;\n sftp.rename(path, backupPath, (err) => {\n if (err && err.message !== 'No such file') {\n // Ignore \"no such file\" error (file doesn't exist yet)\n reject(new Error(`Failed to create backup: ${err.message}`));\n return;\n }\n \n // Write file\n performWrite(backupPath);\n });\n } else {\n performWrite();\n }\n };\n\n const performWrite = (backupPath?: string) => {\n const buffer = Buffer.from(content, encoding as BufferEncoding);\n const tempPath = `${path}.tmp`;\n\n // Write to temp file first (atomic write)\n sftp.writeFile(tempPath, buffer, (err) => {\n if (err) {\n reject(new Error(`Failed to write file: ${err.message}`));\n return;\n }\n\n // Rename temp file to target (atomic operation)\n sftp.rename(tempPath, path, (err) => {\n if (err) {\n logger.error('File rename failed', { tempPath, path, error: err.message });\n reject(new Error(`Failed to rename temp file: ${err.message}`));\n return;\n }\n\n const duration = Date.now() - startTime;\n logger.debug('File write completed', {\n path,\n bytesWritten: buffer.length,\n duration: `${duration}ms`,\n backupPath,\n });\n\n resolve({\n success: true,\n bytesWritten: buffer.length,\n backupPath,\n });\n });\n });\n };\n\n // Create parent directories if requested\n if (createDirs) {\n const dirPath = path.substring(0, path.lastIndexOf('/'));\n this.exec(`mkdir -p ${dirPath}`).then(() => {\n writeOperation();\n }).catch(reject);\n } else {\n writeOperation();\n }\n });\n }\n\n /**\n * Disconnect from the SSH server\n */\n disconnect(): void {\n if (this.connected) {\n logger.info('Disconnecting from SSH server', {\n host: this.config.host,\n username: this.config.username,\n });\n this.client.end();\n this.connected = false;\n }\n }\n\n /**\n * Check if connected\n */\n isConnected(): boolean {\n return this.connected;\n }\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
6
|
-
"names": ["err"]
|
|
3
|
+
"sources": ["../src/server-factory.ts", "../src/tools/acp-remote-list-files.ts", "../src/utils/logger.ts", "../src/tools/acp-remote-execute-command.ts", "../src/tools/acp-remote-read-file.ts", "../src/tools/acp-remote-write-file.ts", "../src/utils/ssh-connection.ts", "../src/types/file-entry.ts"],
|
|
4
|
+
"sourcesContent": ["import { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport {\n CallToolRequestSchema,\n ListToolsRequestSchema,\n} from '@modelcontextprotocol/sdk/types.js';\nimport { config } from './config.js';\nimport { acpRemoteListFilesTool, handleAcpRemoteListFiles } from './tools/acp-remote-list-files.js';\nimport { acpRemoteExecuteCommandTool, handleAcpRemoteExecuteCommand } from './tools/acp-remote-execute-command.js';\nimport { acpRemoteReadFileTool, handleAcpRemoteReadFile } from './tools/acp-remote-read-file.js';\nimport { acpRemoteWriteFileTool, handleAcpRemoteWriteFile } from './tools/acp-remote-write-file.js';\nimport { ServerConfig } from './types/ssh-config.js';\nimport { SSHConnectionManager } from './utils/ssh-connection.js';\nimport { logger } from './utils/logger.js';\n\n/**\n * Create an MCP server instance for a specific user with SSH configuration\n * This factory function is used by mcp-auth for multi-tenant support\n * \n * @param serverConfig - Configuration including userId and SSH credentials\n * @returns Configured MCP Server instance\n */\nexport async function createServer(serverConfig: ServerConfig): Promise<Server> {\n logger.info('Creating server instance', { userId: serverConfig.userId });\n logger.debug('SSH configuration', {\n host: serverConfig.ssh.host,\n port: serverConfig.ssh.port,\n username: serverConfig.ssh.username,\n });\n \n // Create SSH connection manager\n const sshConnection = new SSHConnectionManager(serverConfig.ssh);\n \n // Connect to remote server\n await sshConnection.connect();\n \n logger.info('Server created successfully', { userId: serverConfig.userId });\n\n const server = new Server(\n {\n name: 'acp-mcp',\n version: '0.1.0',\n },\n {\n capabilities: {\n tools: {},\n },\n }\n );\n\n // Register tools with SSH connection context\n server.setRequestHandler(ListToolsRequestSchema, async () => {\n logger.debug('Tool discovery requested', { userId: serverConfig.userId });\n const tools = [acpRemoteListFilesTool, acpRemoteExecuteCommandTool, acpRemoteReadFileTool, acpRemoteWriteFileTool];\n logger.debug(`Returning ${tools.length} tools`, { tools: tools.map(t => t.name), userId: serverConfig.userId });\n return { tools };\n });\n\n server.setRequestHandler(CallToolRequestSchema, async (request) => {\n const startTime = Date.now();\n logger.toolInvoked(request.params.name, request.params.arguments, serverConfig.userId);\n \n try {\n let result;\n \n if (request.params.name === 'acp_remote_list_files') {\n // Pass SSH connection to handler for remote operations\n result = await handleAcpRemoteListFiles(request.params.arguments, sshConnection);\n } else if (request.params.name === 'acp_remote_execute_command') {\n result = await handleAcpRemoteExecuteCommand(request.params.arguments, sshConnection);\n } else if (request.params.name === 'acp_remote_read_file') {\n result = await handleAcpRemoteReadFile(request.params.arguments, sshConnection);\n } else if (request.params.name === 'acp_remote_write_file') {\n result = await handleAcpRemoteWriteFile(request.params.arguments, sshConnection);\n } else {\n throw new Error(`Unknown tool: ${request.params.name}`);\n }\n \n const duration = Date.now() - startTime;\n const resultSize = JSON.stringify(result).length;\n logger.toolCompleted(request.params.name, duration, resultSize);\n \n return result;\n } catch (error) {\n logger.toolFailed(request.params.name, error as Error, request.params.arguments);\n throw error;\n }\n });\n\n // Handle server shutdown to cleanup SSH connection\n process.on('SIGINT', () => {\n logger.info('Received SIGINT, shutting down', { userId: serverConfig.userId });\n sshConnection.disconnect();\n process.exit(0);\n });\n\n process.on('SIGTERM', () => {\n logger.info('Received SIGTERM, shutting down', { userId: serverConfig.userId });\n sshConnection.disconnect();\n process.exit(0);\n });\n\n return server;\n}\n", "import { Tool } from '@modelcontextprotocol/sdk/types.js';\nimport { SSHConnectionManager } from '../utils/ssh-connection.js';\nimport { FileEntry } from '../types/file-entry.js';\n\nexport const acpRemoteListFilesTool: Tool = {\n name: 'acp_remote_list_files',\n description: 'List files and directories in a specified path on the remote machine via SSH. Returns comprehensive metadata including permissions, timestamps, size, and ownership. Includes hidden files by default.',\n inputSchema: {\n type: 'object',\n properties: {\n path: {\n type: 'string',\n description: 'The directory path to list files from',\n },\n recursive: {\n type: 'boolean',\n description: 'Whether to list files recursively',\n default: false,\n },\n includeHidden: {\n type: 'boolean',\n description: 'Whether to include hidden files (starting with .)',\n default: true,\n },\n },\n required: ['path'],\n },\n};\n\ninterface ListFilesArgs {\n path: string;\n recursive?: boolean;\n includeHidden?: boolean;\n}\n\n/**\n * Handle the acp_remote_list_files tool invocation\n * Lists files and directories at the specified path on the remote machine via SSH\n * \n * @param args - Tool arguments containing path and recursive flag\n * @param sshConnection - SSH connection manager for remote operations\n */\nexport async function handleAcpRemoteListFiles(\n args: any,\n sshConnection: SSHConnectionManager\n): Promise<{ content: Array<{ type: string; text: string }> }> {\n const { path, recursive = false, includeHidden = true } = args as ListFilesArgs;\n\n try {\n const entries = await listRemoteFiles(sshConnection, path, recursive, includeHidden);\n \n // Format as JSON for structured output\n const output = JSON.stringify(entries, null, 2);\n \n return {\n content: [\n {\n type: 'text',\n text: output,\n },\n ],\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n return {\n content: [\n {\n type: 'text',\n text: `Error listing remote files: ${errorMessage}`,\n },\n ],\n };\n }\n}\n\n/**\n * Recursively list files in a remote directory via SSH\n * Returns FileEntry objects with comprehensive metadata\n */\nasync function listRemoteFiles(\n ssh: SSHConnectionManager,\n dirPath: string,\n recursive: boolean,\n includeHidden: boolean\n): Promise<FileEntry[]> {\n const entries = await ssh.listFiles(dirPath, includeHidden);\n const allEntries: FileEntry[] = [...entries];\n\n // Recursively list subdirectories if requested\n if (recursive) {\n for (const entry of entries) {\n if (entry.type === 'directory') {\n const subEntries = await listRemoteFiles(ssh, entry.path, recursive, includeHidden);\n allEntries.push(...subEntries);\n }\n }\n }\n\n return allEntries;\n}\n", "/**\n * Logger utility for ACP MCP Server\n * Provides structured logging with configurable log levels\n */\n\nexport type LogLevel = 'error' | 'warn' | 'info' | 'debug' | 'trace';\n\nconst LOG_LEVELS: Record<LogLevel, number> = {\n error: 0,\n warn: 1,\n info: 2,\n debug: 3,\n trace: 4,\n};\n\nclass Logger {\n private level: LogLevel;\n private enabled: boolean;\n\n constructor() {\n // Read configuration from environment variables\n this.level = (process.env.ACP_MCP_LOG_LEVEL as LogLevel) || 'info';\n this.enabled = process.env.ACP_MCP_DEBUG === 'true' || process.env.NODE_ENV === 'development';\n }\n\n private shouldLog(level: LogLevel): boolean {\n if (!this.enabled && level !== 'error' && level !== 'warn') {\n return false;\n }\n return LOG_LEVELS[level] <= LOG_LEVELS[this.level];\n }\n\n private formatMessage(level: LogLevel, message: string, data?: any): string {\n const timestamp = new Date().toISOString();\n const prefix = `[${timestamp}] [${level.toUpperCase()}]`;\n \n if (data !== undefined) {\n const dataStr = typeof data === 'object' ? JSON.stringify(data, null, 2) : String(data);\n return `${prefix} ${message}\\n${dataStr}`;\n }\n \n return `${prefix} ${message}`;\n }\n\n error(message: string, data?: any): void {\n if (this.shouldLog('error')) {\n console.error(this.formatMessage('error', message, data));\n }\n }\n\n warn(message: string, data?: any): void {\n if (this.shouldLog('warn')) {\n console.error(this.formatMessage('warn', message, data));\n }\n }\n\n info(message: string, data?: any): void {\n if (this.shouldLog('info')) {\n console.error(this.formatMessage('info', message, data));\n }\n }\n\n debug(message: string, data?: any): void {\n if (this.shouldLog('debug')) {\n console.error(this.formatMessage('debug', message, data));\n }\n }\n\n trace(message: string, data?: any): void {\n if (this.shouldLog('trace')) {\n console.error(this.formatMessage('trace', message, data));\n }\n }\n\n /**\n * Log tool invocation with parameters\n */\n toolInvoked(toolName: string, params: any, userId?: string): void {\n this.info(`Tool invoked: ${toolName}`);\n this.debug('Tool parameters', { tool: toolName, params, userId });\n }\n\n /**\n * Log tool completion with result summary\n */\n toolCompleted(toolName: string, duration: number, resultSize?: number): void {\n this.info(`Tool completed: ${toolName}`);\n this.debug('Tool performance', { tool: toolName, duration: `${duration}ms`, resultSize });\n }\n\n /**\n * Log tool failure with error details\n */\n toolFailed(toolName: string, error: Error, params?: any): void {\n this.error(`Tool execution failed: ${toolName}`, {\n tool: toolName,\n error: error.message,\n stack: error.stack,\n params,\n });\n }\n\n /**\n * Log SSH command execution\n */\n sshCommand(command: string, cwd?: string, timeout?: number): void {\n this.debug('Executing SSH command', { command, cwd, timeout });\n }\n\n /**\n * Log SSH command result\n */\n sshCommandResult(exitCode: number, duration: number, stdoutSize: number, stderrSize: number): void {\n this.debug('SSH command completed', {\n exitCode,\n duration: `${duration}ms`,\n stdout: `${stdoutSize} bytes`,\n stderr: `${stderrSize} bytes`,\n });\n }\n\n /**\n * Log file operation\n */\n fileOperation(operation: string, path: string, details?: any): void {\n this.info(`File operation: ${operation}`, { path, ...details });\n }\n}\n\n// Export singleton instance\nexport const logger = new Logger();\n", "import { Tool } from '@modelcontextprotocol/sdk/types.js';\nimport { SSHConnectionManager } from '../utils/ssh-connection.js';\nimport { logger } from '../utils/logger.js';\n\nexport const acpRemoteExecuteCommandTool: Tool = {\n name: 'acp_remote_execute_command',\n description: 'Execute a shell command on the remote machine via SSH',\n inputSchema: {\n type: 'object',\n properties: {\n command: {\n type: 'string',\n description: 'Shell command to execute',\n },\n cwd: {\n type: 'string',\n description: 'Working directory for command execution (optional)',\n },\n timeout: {\n type: 'number',\n description: 'Timeout in seconds (default: 30)',\n default: 30,\n },\n },\n required: ['command'],\n },\n};\n\ninterface ExecuteCommandArgs {\n command: string;\n cwd?: string;\n timeout?: number;\n}\n\ninterface ExecuteCommandResult {\n stdout: string;\n stderr: string;\n exitCode: number;\n timedOut: boolean;\n}\n\n/**\n * Handle the acp_remote_execute_command tool invocation\n * Executes a shell command on the remote machine via SSH\n * \n * @param args - Tool arguments containing command, cwd, and timeout\n * @param sshConnection - SSH connection manager for remote operations\n */\nexport async function handleAcpRemoteExecuteCommand(\n args: any,\n sshConnection: SSHConnectionManager\n): Promise<{ content: Array<{ type: string; text: string }> }> {\n const { command, cwd, timeout = 30 } = args as ExecuteCommandArgs;\n\n logger.debug('Executing remote command', { command, cwd, timeout });\n\n try {\n // Build command with working directory if specified\n const fullCommand = cwd ? `cd ${cwd} && ${command}` : command;\n \n const result = await sshConnection.execWithTimeout(fullCommand, timeout);\n \n logger.debug('Command execution result', {\n exitCode: result.exitCode,\n timedOut: result.timedOut,\n stdoutLength: result.stdout.length,\n stderrLength: result.stderr.length,\n });\n \n // Format output as JSON for structured response\n const output: ExecuteCommandResult = {\n stdout: result.stdout,\n stderr: result.stderr,\n exitCode: result.exitCode,\n timedOut: result.timedOut,\n };\n \n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(output, null, 2),\n },\n ],\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n logger.error('Command execution error', { command, error: errorMessage });\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify({\n stdout: '',\n stderr: errorMessage,\n exitCode: 1,\n timedOut: false,\n }, null, 2),\n },\n ],\n };\n }\n}\n", "import { Tool } from '@modelcontextprotocol/sdk/types.js';\nimport { SSHConnectionManager } from '../utils/ssh-connection.js';\nimport { logger } from '../utils/logger.js';\n\nexport const acpRemoteReadFileTool: Tool = {\n name: 'acp_remote_read_file',\n description: 'Read file contents from the remote machine via SSH',\n inputSchema: {\n type: 'object',\n properties: {\n path: {\n type: 'string',\n description: 'Absolute path to file',\n },\n encoding: {\n type: 'string',\n description: 'File encoding (default: utf-8)',\n default: 'utf-8',\n enum: ['utf-8', 'ascii', 'base64'],\n },\n maxSize: {\n type: 'number',\n description: 'Max file size in bytes (default: 1MB)',\n default: 1048576,\n },\n },\n required: ['path'],\n },\n};\n\ninterface ReadFileArgs {\n path: string;\n encoding?: string;\n maxSize?: number;\n}\n\ninterface ReadFileResult {\n content: string;\n size: number;\n encoding: string;\n}\n\n/**\n * Handle the acp_remote_read_file tool invocation\n * Reads file contents from the remote machine via SSH\n * \n * @param args - Tool arguments containing path, encoding, and maxSize\n * @param sshConnection - SSH connection manager for remote operations\n */\nexport async function handleAcpRemoteReadFile(\n args: any,\n sshConnection: SSHConnectionManager\n): Promise<{ content: Array<{ type: string; text: string }> }> {\n const { path, encoding = 'utf-8', maxSize = 1048576 } = args as ReadFileArgs;\n\n logger.debug('Reading remote file', { path, encoding, maxSize });\n\n try {\n const result = await sshConnection.readFile(path, encoding, maxSize);\n \n logger.debug('File read successful', { path, size: result.size });\n \n const output: ReadFileResult = {\n content: result.content,\n size: result.size,\n encoding: result.encoding,\n };\n \n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(output, null, 2),\n },\n ],\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n logger.error('File read error', { path, error: errorMessage });\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify({\n error: errorMessage,\n content: '',\n size: 0,\n encoding,\n }, null, 2),\n },\n ],\n };\n }\n}\n", "import { Tool } from '@modelcontextprotocol/sdk/types.js';\nimport { SSHConnectionManager } from '../utils/ssh-connection.js';\nimport { logger } from '../utils/logger.js';\n\nexport const acpRemoteWriteFileTool: Tool = {\n name: 'acp_remote_write_file',\n description: 'Write file contents to the remote machine via SSH',\n inputSchema: {\n type: 'object',\n properties: {\n path: {\n type: 'string',\n description: 'Absolute path to file',\n },\n content: {\n type: 'string',\n description: 'File contents to write',\n },\n encoding: {\n type: 'string',\n description: 'File encoding (default: utf-8)',\n default: 'utf-8',\n },\n createDirs: {\n type: 'boolean',\n description: 'Create parent directories if they don\\'t exist (default: false)',\n default: false,\n },\n backup: {\n type: 'boolean',\n description: 'Backup existing file before overwriting (default: false)',\n default: false,\n },\n },\n required: ['path', 'content'],\n },\n};\n\ninterface WriteFileArgs {\n path: string;\n content: string;\n encoding?: string;\n createDirs?: boolean;\n backup?: boolean;\n}\n\ninterface WriteFileResult {\n success: boolean;\n bytesWritten: number;\n backupPath?: string;\n}\n\n/**\n * Handle the acp_remote_write_file tool invocation\n * Writes file contents to the remote machine via SSH\n * \n * @param args - Tool arguments containing path, content, and options\n * @param sshConnection - SSH connection manager for remote operations\n */\nexport async function handleAcpRemoteWriteFile(\n args: any,\n sshConnection: SSHConnectionManager\n): Promise<{ content: Array<{ type: string; text: string }> }> {\n const { path, content, encoding = 'utf-8', createDirs = false, backup = false } = args as WriteFileArgs;\n\n logger.debug('Writing remote file', { path, contentSize: content.length, encoding, createDirs, backup });\n\n try {\n const result = await sshConnection.writeFile(path, content, {\n encoding,\n createDirs,\n backup,\n });\n \n logger.debug('File write successful', { path, bytesWritten: result.bytesWritten, backupPath: result.backupPath });\n \n const output: WriteFileResult = {\n success: result.success,\n bytesWritten: result.bytesWritten,\n backupPath: result.backupPath,\n };\n \n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(output, null, 2),\n },\n ],\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n logger.error('File write error', { path, error: errorMessage });\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify({\n success: false,\n bytesWritten: 0,\n error: errorMessage,\n }, null, 2),\n },\n ],\n };\n }\n}\n", "import { Client, SFTPWrapper } from 'ssh2';\nimport { SSHConfig } from '../types/ssh-config.js';\nimport { FileEntry, parsePermissions, getFileType } from '../types/file-entry.js';\nimport { logger } from './logger.js';\n\n/**\n * SSH Connection Manager\n * Manages SSH connections and provides SFTP access for remote file operations\n */\nexport class SSHConnectionManager {\n private client: Client;\n private config: SSHConfig;\n private connected: boolean = false;\n\n constructor(config: SSHConfig) {\n this.config = config;\n this.client = new Client();\n }\n\n /**\n * Connect to the remote SSH server\n */\n async connect(): Promise<void> {\n if (this.connected) {\n logger.debug('SSH connection already established');\n return;\n }\n\n logger.info('Connecting to SSH server', {\n host: this.config.host,\n port: this.config.port || 22,\n username: this.config.username,\n });\n\n return new Promise((resolve, reject) => {\n this.client\n .on('ready', () => {\n this.connected = true;\n logger.info('SSH connection established', {\n host: this.config.host,\n username: this.config.username,\n });\n resolve();\n })\n .on('error', (err) => {\n logger.error('SSH connection failed', {\n host: this.config.host,\n error: err.message,\n });\n reject(err);\n })\n .connect({\n host: this.config.host,\n port: this.config.port || 22,\n username: this.config.username,\n privateKey: this.config.privateKey,\n });\n });\n }\n\n /**\n * Execute a command on the remote server\n */\n async exec(command: string): Promise<string> {\n if (!this.connected) {\n await this.connect();\n }\n\n return new Promise((resolve, reject) => {\n this.client.exec(command, (err, stream) => {\n if (err) {\n reject(err);\n return;\n }\n\n let stdout = '';\n let stderr = '';\n\n stream\n .on('close', (code: number) => {\n if (code !== 0) {\n reject(new Error(`Command failed with code ${code}: ${stderr}`));\n } else {\n resolve(stdout);\n }\n })\n .on('data', (data: Buffer) => {\n stdout += data.toString();\n })\n .stderr.on('data', (data: Buffer) => {\n stderr += data.toString();\n });\n });\n });\n }\n\n /**\n * Execute a command on the remote server with timeout support\n */\n async execWithTimeout(\n command: string,\n timeoutSeconds: number = 30\n ): Promise<{ stdout: string; stderr: string; exitCode: number; timedOut: boolean }> {\n if (!this.connected) {\n await this.connect();\n }\n\n const startTime = Date.now();\n logger.sshCommand(command, undefined, timeoutSeconds);\n\n const execPromise = new Promise<{ stdout: string; stderr: string; exitCode: number }>((resolve, reject) => {\n this.client.exec(command, (err, stream) => {\n if (err) {\n reject(err);\n return;\n }\n\n let stdout = '';\n let stderr = '';\n\n stream\n .on('close', (code: number) => {\n const duration = Date.now() - startTime;\n logger.sshCommandResult(code, duration, stdout.length, stderr.length);\n resolve({ stdout, stderr, exitCode: code });\n })\n .on('data', (data: Buffer) => {\n stdout += data.toString();\n })\n .stderr.on('data', (data: Buffer) => {\n stderr += data.toString();\n });\n });\n });\n\n const timeoutPromise = new Promise<never>((_, reject) => {\n setTimeout(() => {\n reject(new Error('Command execution timed out'));\n }, timeoutSeconds * 1000);\n });\n\n try {\n const result = await Promise.race([execPromise, timeoutPromise]);\n return { ...result, timedOut: false };\n } catch (error) {\n if (error instanceof Error && error.message === 'Command execution timed out') {\n logger.warn('SSH command timed out', { command, timeout: timeoutSeconds });\n return {\n stdout: '',\n stderr: 'Command execution timed out',\n exitCode: 124,\n timedOut: true,\n };\n }\n logger.error('SSH command execution failed', {\n command,\n error: error instanceof Error ? error.message : String(error),\n });\n throw error;\n }\n }\n\n /**\n * Get SFTP wrapper for file operations\n */\n async getSFTP(): Promise<SFTPWrapper> {\n if (!this.connected) {\n await this.connect();\n }\n\n return new Promise((resolve, reject) => {\n this.client.sftp((err, sftp) => {\n if (err) {\n reject(err);\n } else {\n resolve(sftp);\n }\n });\n });\n }\n\n /**\n * List files in a directory with comprehensive metadata\n * Uses hybrid approach: shell ls for filenames (includes hidden), SFTP stat for metadata\n *\n * @param path - Directory path to list\n * @param includeHidden - Whether to include hidden files (default: true)\n * @returns Array of FileEntry objects with complete metadata\n */\n async listFiles(path: string, includeHidden: boolean = true): Promise<FileEntry[]> {\n const startTime = Date.now();\n logger.debug('Listing files', { path, includeHidden });\n\n try {\n // Step 1: Use shell command to get ALL filenames (including hidden)\n const lsFlag = includeHidden ? '-A' : '';\n const command = `ls ${lsFlag} -1 \"${path}\" 2>/dev/null`;\n const result = await this.execWithTimeout(command, 10);\n\n if (result.exitCode !== 0) {\n throw new Error(`ls command failed: ${result.stderr}`);\n }\n\n const filenames = result.stdout\n .split('\\n')\n .map(f => f.trim())\n .filter(f => f !== '' && f !== '.' && f !== '..');\n\n logger.debug('Filenames retrieved via shell', {\n path,\n count: filenames.length,\n method: 'shell',\n });\n\n // Step 2: Get rich metadata for each file using SFTP stat()\n const sftp = await this.getSFTP();\n const entries: FileEntry[] = [];\n\n for (const filename of filenames) {\n const fullPath = `${path}/${filename}`.replace(/\\/+/g, '/');\n\n try {\n const stats = await new Promise<any>((resolve, reject) => {\n sftp.stat(fullPath, (err, stats) => {\n if (err) reject(err);\n else resolve(stats);\n });\n });\n\n entries.push({\n name: filename,\n path: fullPath,\n type: getFileType(stats),\n size: stats.size,\n permissions: parsePermissions(stats.mode),\n owner: {\n uid: stats.uid,\n gid: stats.gid,\n },\n timestamps: {\n accessed: new Date(stats.atime * 1000).toISOString(),\n modified: new Date(stats.mtime * 1000).toISOString(),\n },\n });\n } catch (error) {\n // Skip files we can't stat (permissions, race conditions, etc.)\n logger.warn('Failed to stat file, skipping', {\n path: fullPath,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n\n const duration = Date.now() - startTime;\n logger.debug('Files listed successfully', {\n path,\n count: entries.length,\n duration: `${duration}ms`,\n method: 'hybrid',\n });\n\n return entries;\n } catch (error) {\n // Fallback to SFTP readdir if shell command fails\n logger.warn('Shell ls command failed, falling back to SFTP readdir', {\n path,\n error: error instanceof Error ? error.message : String(error),\n });\n\n return this.listFilesViaSFTP(path, includeHidden);\n }\n }\n\n /**\n * Fallback method: List files using SFTP readdir (may miss hidden files)\n * @private\n */\n private async listFilesViaSFTP(path: string, includeHidden: boolean): Promise<FileEntry[]> {\n const sftp = await this.getSFTP();\n\n return new Promise((resolve, reject) => {\n sftp.readdir(path, (err, list) => {\n if (err) {\n logger.error('SFTP readdir failed', { path, error: err.message });\n reject(err);\n return;\n }\n\n let entries = list.map((item) => ({\n name: item.filename,\n path: `${path}/${item.filename}`.replace(/\\/+/g, '/'),\n type: getFileType(item.attrs),\n size: item.attrs.size,\n permissions: parsePermissions(item.attrs.mode),\n owner: {\n uid: item.attrs.uid,\n gid: item.attrs.gid,\n },\n timestamps: {\n accessed: new Date(item.attrs.atime * 1000).toISOString(),\n modified: new Date(item.attrs.mtime * 1000).toISOString(),\n },\n }));\n\n // SFTP readdir doesn't return hidden files, so filter if requested\n if (!includeHidden) {\n entries = entries.filter(e => !e.name.startsWith('.'));\n }\n\n logger.debug('Files listed via SFTP fallback', {\n path,\n count: entries.length,\n method: 'sftp',\n note: 'Hidden files may be missing (SFTP limitation)',\n });\n\n resolve(entries);\n });\n });\n }\n\n /**\n * Read file contents from remote machine\n */\n async readFile(\n path: string,\n encoding: string = 'utf-8',\n maxSize: number = 1048576\n ): Promise<{ content: string; size: number; encoding: string }> {\n const startTime = Date.now();\n logger.fileOperation('read', path, { encoding, maxSize });\n \n const sftp = await this.getSFTP();\n\n return new Promise((resolve, reject) => {\n // First, get file stats to check size\n sftp.stat(path, (err, stats) => {\n if (err) {\n logger.error('File stat failed', { path, error: err.message });\n reject(new Error(`File not found or inaccessible: ${path}`));\n return;\n }\n\n logger.debug('File stat retrieved', { path, size: stats.size });\n\n if (stats.size > maxSize) {\n logger.warn('File too large', { path, size: stats.size, maxSize });\n reject(new Error(`File too large: ${stats.size} bytes (max: ${maxSize} bytes)`));\n return;\n }\n\n // Read file contents\n sftp.readFile(path, { encoding: encoding as BufferEncoding }, (err, data) => {\n if (err) {\n logger.error('File read failed', { path, error: err.message });\n reject(new Error(`Failed to read file: ${err.message}`));\n return;\n }\n\n const duration = Date.now() - startTime;\n logger.debug('File read completed', { path, size: stats.size, duration: `${duration}ms` });\n\n resolve({\n content: data.toString(),\n size: stats.size,\n encoding,\n });\n });\n });\n });\n }\n\n /**\n * Write file contents to remote machine\n */\n async writeFile(\n path: string,\n content: string,\n options: {\n encoding?: string;\n createDirs?: boolean;\n backup?: boolean;\n } = {}\n ): Promise<{ success: boolean; bytesWritten: number; backupPath?: string }> {\n const { encoding = 'utf-8', createDirs = false, backup = false } = options;\n const startTime = Date.now();\n \n logger.fileOperation('write', path, {\n contentSize: content.length,\n encoding,\n createDirs,\n backup,\n });\n \n const sftp = await this.getSFTP();\n\n return new Promise((resolve, reject) => {\n const writeOperation = () => {\n // Create backup if requested\n if (backup) {\n const backupPath = `${path}.backup`;\n sftp.rename(path, backupPath, (err) => {\n if (err && err.message !== 'No such file') {\n // Ignore \"no such file\" error (file doesn't exist yet)\n reject(new Error(`Failed to create backup: ${err.message}`));\n return;\n }\n \n // Write file\n performWrite(backupPath);\n });\n } else {\n performWrite();\n }\n };\n\n const performWrite = (backupPath?: string) => {\n const buffer = Buffer.from(content, encoding as BufferEncoding);\n const tempPath = `${path}.tmp`;\n\n // Write to temp file first (atomic write)\n sftp.writeFile(tempPath, buffer, (err) => {\n if (err) {\n reject(new Error(`Failed to write file: ${err.message}`));\n return;\n }\n\n // Rename temp file to target (atomic operation)\n sftp.rename(tempPath, path, (err) => {\n if (err) {\n logger.error('File rename failed', { tempPath, path, error: err.message });\n reject(new Error(`Failed to rename temp file: ${err.message}`));\n return;\n }\n\n const duration = Date.now() - startTime;\n logger.debug('File write completed', {\n path,\n bytesWritten: buffer.length,\n duration: `${duration}ms`,\n backupPath,\n });\n\n resolve({\n success: true,\n bytesWritten: buffer.length,\n backupPath,\n });\n });\n });\n };\n\n // Create parent directories if requested\n if (createDirs) {\n const dirPath = path.substring(0, path.lastIndexOf('/'));\n this.exec(`mkdir -p ${dirPath}`).then(() => {\n writeOperation();\n }).catch(reject);\n } else {\n writeOperation();\n }\n });\n }\n\n /**\n * Disconnect from the SSH server\n */\n disconnect(): void {\n if (this.connected) {\n logger.info('Disconnecting from SSH server', {\n host: this.config.host,\n username: this.config.username,\n });\n this.client.end();\n this.connected = false;\n }\n }\n\n /**\n * Check if connected\n */\n isConnected(): boolean {\n return this.connected;\n }\n}\n", "/**\n * Comprehensive file entry with metadata\n */\nexport interface FileEntry {\n /** Filename (without path) */\n name: string;\n \n /** Absolute path to file */\n path: string;\n \n /** File type */\n type: 'file' | 'directory' | 'symlink' | 'other';\n \n /** File size in bytes */\n size: number;\n \n /** File permissions */\n permissions: {\n /** Octal mode (e.g., 0o644) */\n mode: number;\n \n /** Human-readable string (e.g., \"rw-r--r--\") */\n string: string;\n \n /** Owner permissions */\n owner: {\n read: boolean;\n write: boolean;\n execute: boolean;\n };\n \n /** Group permissions */\n group: {\n read: boolean;\n write: boolean;\n execute: boolean;\n };\n \n /** Others permissions */\n others: {\n read: boolean;\n write: boolean;\n execute: boolean;\n };\n };\n \n /** File ownership */\n owner: {\n /** User ID */\n uid: number;\n \n /** Group ID */\n gid: number;\n };\n \n /** File timestamps */\n timestamps: {\n /** Last access time (ISO 8601) */\n accessed: string;\n \n /** Last modification time (ISO 8601) */\n modified: string;\n };\n}\n\n/**\n * Convert Unix mode to human-readable permission string\n * @param mode - Unix file mode (e.g., 33188 for -rw-r--r--)\n * @returns Permission string (e.g., \"rw-r--r--\")\n */\nexport function modeToPermissionString(mode: number): string {\n const perms = [\n (mode & 0o400) ? 'r' : '-',\n (mode & 0o200) ? 'w' : '-',\n (mode & 0o100) ? 'x' : '-',\n (mode & 0o040) ? 'r' : '-',\n (mode & 0o020) ? 'w' : '-',\n (mode & 0o010) ? 'x' : '-',\n (mode & 0o004) ? 'r' : '-',\n (mode & 0o002) ? 'w' : '-',\n (mode & 0o001) ? 'x' : '-',\n ];\n return perms.join('');\n}\n\n/**\n * Parse Unix mode into structured permissions object\n * @param mode - Unix file mode\n * @returns Structured permissions object\n */\nexport function parsePermissions(mode: number) {\n return {\n mode,\n string: modeToPermissionString(mode),\n owner: {\n read: (mode & 0o400) !== 0,\n write: (mode & 0o200) !== 0,\n execute: (mode & 0o100) !== 0,\n },\n group: {\n read: (mode & 0o040) !== 0,\n write: (mode & 0o020) !== 0,\n execute: (mode & 0o010) !== 0,\n },\n others: {\n read: (mode & 0o004) !== 0,\n write: (mode & 0o002) !== 0,\n execute: (mode & 0o001) !== 0,\n },\n };\n}\n\n/**\n * Determine file type from SFTP stats\n * @param stats - SFTP file stats\n * @returns File type string\n */\nexport function getFileType(stats: any): 'file' | 'directory' | 'symlink' | 'other' {\n if (stats.isDirectory()) return 'directory';\n if (stats.isFile()) return 'file';\n if (stats.isSymbolicLink()) return 'symlink';\n return 'other';\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["stats", "err"]
|
|
7
7
|
}
|
package/dist/server.js
CHANGED
|
@@ -436,7 +436,7 @@ function loadSSHPrivateKey() {
|
|
|
436
436
|
// src/tools/acp-remote-list-files.ts
|
|
437
437
|
var acpRemoteListFilesTool = {
|
|
438
438
|
name: "acp_remote_list_files",
|
|
439
|
-
description: "List files and directories in a specified path on the remote machine via SSH",
|
|
439
|
+
description: "List files and directories in a specified path on the remote machine via SSH. Returns comprehensive metadata including permissions, timestamps, size, and ownership. Includes hidden files by default.",
|
|
440
440
|
inputSchema: {
|
|
441
441
|
type: "object",
|
|
442
442
|
properties: {
|
|
@@ -448,20 +448,26 @@ var acpRemoteListFilesTool = {
|
|
|
448
448
|
type: "boolean",
|
|
449
449
|
description: "Whether to list files recursively",
|
|
450
450
|
default: false
|
|
451
|
+
},
|
|
452
|
+
includeHidden: {
|
|
453
|
+
type: "boolean",
|
|
454
|
+
description: "Whether to include hidden files (starting with .)",
|
|
455
|
+
default: true
|
|
451
456
|
}
|
|
452
457
|
},
|
|
453
458
|
required: ["path"]
|
|
454
459
|
}
|
|
455
460
|
};
|
|
456
461
|
async function handleAcpRemoteListFiles(args, sshConnection) {
|
|
457
|
-
const { path, recursive = false } = args;
|
|
462
|
+
const { path, recursive = false, includeHidden = true } = args;
|
|
458
463
|
try {
|
|
459
|
-
const
|
|
464
|
+
const entries = await listRemoteFiles(sshConnection, path, recursive, includeHidden);
|
|
465
|
+
const output = JSON.stringify(entries, null, 2);
|
|
460
466
|
return {
|
|
461
467
|
content: [
|
|
462
468
|
{
|
|
463
469
|
type: "text",
|
|
464
|
-
text:
|
|
470
|
+
text: output
|
|
465
471
|
}
|
|
466
472
|
]
|
|
467
473
|
};
|
|
@@ -477,22 +483,18 @@ async function handleAcpRemoteListFiles(args, sshConnection) {
|
|
|
477
483
|
};
|
|
478
484
|
}
|
|
479
485
|
}
|
|
480
|
-
async function listRemoteFiles(ssh, dirPath, recursive) {
|
|
481
|
-
const entries = await ssh.listFiles(dirPath);
|
|
482
|
-
const
|
|
483
|
-
|
|
484
|
-
const
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
const subFiles = await listRemoteFiles(ssh, fullPath, recursive);
|
|
489
|
-
files.push(...subFiles);
|
|
486
|
+
async function listRemoteFiles(ssh, dirPath, recursive, includeHidden) {
|
|
487
|
+
const entries = await ssh.listFiles(dirPath, includeHidden);
|
|
488
|
+
const allEntries = [...entries];
|
|
489
|
+
if (recursive) {
|
|
490
|
+
for (const entry of entries) {
|
|
491
|
+
if (entry.type === "directory") {
|
|
492
|
+
const subEntries = await listRemoteFiles(ssh, entry.path, recursive, includeHidden);
|
|
493
|
+
allEntries.push(...subEntries);
|
|
490
494
|
}
|
|
491
|
-
} else {
|
|
492
|
-
files.push(fullPath);
|
|
493
495
|
}
|
|
494
496
|
}
|
|
495
|
-
return
|
|
497
|
+
return allEntries;
|
|
496
498
|
}
|
|
497
499
|
|
|
498
500
|
// src/utils/logger.ts
|
|
@@ -812,6 +814,54 @@ async function handleAcpRemoteWriteFile(args, sshConnection) {
|
|
|
812
814
|
|
|
813
815
|
// src/utils/ssh-connection.ts
|
|
814
816
|
import { Client } from "ssh2";
|
|
817
|
+
|
|
818
|
+
// src/types/file-entry.ts
|
|
819
|
+
function modeToPermissionString(mode) {
|
|
820
|
+
const perms = [
|
|
821
|
+
mode & 256 ? "r" : "-",
|
|
822
|
+
mode & 128 ? "w" : "-",
|
|
823
|
+
mode & 64 ? "x" : "-",
|
|
824
|
+
mode & 32 ? "r" : "-",
|
|
825
|
+
mode & 16 ? "w" : "-",
|
|
826
|
+
mode & 8 ? "x" : "-",
|
|
827
|
+
mode & 4 ? "r" : "-",
|
|
828
|
+
mode & 2 ? "w" : "-",
|
|
829
|
+
mode & 1 ? "x" : "-"
|
|
830
|
+
];
|
|
831
|
+
return perms.join("");
|
|
832
|
+
}
|
|
833
|
+
function parsePermissions(mode) {
|
|
834
|
+
return {
|
|
835
|
+
mode,
|
|
836
|
+
string: modeToPermissionString(mode),
|
|
837
|
+
owner: {
|
|
838
|
+
read: (mode & 256) !== 0,
|
|
839
|
+
write: (mode & 128) !== 0,
|
|
840
|
+
execute: (mode & 64) !== 0
|
|
841
|
+
},
|
|
842
|
+
group: {
|
|
843
|
+
read: (mode & 32) !== 0,
|
|
844
|
+
write: (mode & 16) !== 0,
|
|
845
|
+
execute: (mode & 8) !== 0
|
|
846
|
+
},
|
|
847
|
+
others: {
|
|
848
|
+
read: (mode & 4) !== 0,
|
|
849
|
+
write: (mode & 2) !== 0,
|
|
850
|
+
execute: (mode & 1) !== 0
|
|
851
|
+
}
|
|
852
|
+
};
|
|
853
|
+
}
|
|
854
|
+
function getFileType(stats) {
|
|
855
|
+
if (stats.isDirectory())
|
|
856
|
+
return "directory";
|
|
857
|
+
if (stats.isFile())
|
|
858
|
+
return "file";
|
|
859
|
+
if (stats.isSymbolicLink())
|
|
860
|
+
return "symlink";
|
|
861
|
+
return "other";
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
// src/utils/ssh-connection.ts
|
|
815
865
|
var SSHConnectionManager = class {
|
|
816
866
|
client;
|
|
817
867
|
config;
|
|
@@ -955,21 +1005,118 @@ var SSHConnectionManager = class {
|
|
|
955
1005
|
});
|
|
956
1006
|
}
|
|
957
1007
|
/**
|
|
958
|
-
* List files in a directory
|
|
1008
|
+
* List files in a directory with comprehensive metadata
|
|
1009
|
+
* Uses hybrid approach: shell ls for filenames (includes hidden), SFTP stat for metadata
|
|
1010
|
+
*
|
|
1011
|
+
* @param path - Directory path to list
|
|
1012
|
+
* @param includeHidden - Whether to include hidden files (default: true)
|
|
1013
|
+
* @returns Array of FileEntry objects with complete metadata
|
|
959
1014
|
*/
|
|
960
|
-
async listFiles(path) {
|
|
1015
|
+
async listFiles(path, includeHidden = true) {
|
|
1016
|
+
const startTime = Date.now();
|
|
1017
|
+
logger.debug("Listing files", { path, includeHidden });
|
|
1018
|
+
try {
|
|
1019
|
+
const lsFlag = includeHidden ? "-A" : "";
|
|
1020
|
+
const command = `ls ${lsFlag} -1 "${path}" 2>/dev/null`;
|
|
1021
|
+
const result = await this.execWithTimeout(command, 10);
|
|
1022
|
+
if (result.exitCode !== 0) {
|
|
1023
|
+
throw new Error(`ls command failed: ${result.stderr}`);
|
|
1024
|
+
}
|
|
1025
|
+
const filenames = result.stdout.split("\n").map((f) => f.trim()).filter((f) => f !== "" && f !== "." && f !== "..");
|
|
1026
|
+
logger.debug("Filenames retrieved via shell", {
|
|
1027
|
+
path,
|
|
1028
|
+
count: filenames.length,
|
|
1029
|
+
method: "shell"
|
|
1030
|
+
});
|
|
1031
|
+
const sftp = await this.getSFTP();
|
|
1032
|
+
const entries = [];
|
|
1033
|
+
for (const filename of filenames) {
|
|
1034
|
+
const fullPath = `${path}/${filename}`.replace(/\/+/g, "/");
|
|
1035
|
+
try {
|
|
1036
|
+
const stats = await new Promise((resolve, reject) => {
|
|
1037
|
+
sftp.stat(fullPath, (err, stats2) => {
|
|
1038
|
+
if (err)
|
|
1039
|
+
reject(err);
|
|
1040
|
+
else
|
|
1041
|
+
resolve(stats2);
|
|
1042
|
+
});
|
|
1043
|
+
});
|
|
1044
|
+
entries.push({
|
|
1045
|
+
name: filename,
|
|
1046
|
+
path: fullPath,
|
|
1047
|
+
type: getFileType(stats),
|
|
1048
|
+
size: stats.size,
|
|
1049
|
+
permissions: parsePermissions(stats.mode),
|
|
1050
|
+
owner: {
|
|
1051
|
+
uid: stats.uid,
|
|
1052
|
+
gid: stats.gid
|
|
1053
|
+
},
|
|
1054
|
+
timestamps: {
|
|
1055
|
+
accessed: new Date(stats.atime * 1e3).toISOString(),
|
|
1056
|
+
modified: new Date(stats.mtime * 1e3).toISOString()
|
|
1057
|
+
}
|
|
1058
|
+
});
|
|
1059
|
+
} catch (error) {
|
|
1060
|
+
logger.warn("Failed to stat file, skipping", {
|
|
1061
|
+
path: fullPath,
|
|
1062
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1063
|
+
});
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
const duration = Date.now() - startTime;
|
|
1067
|
+
logger.debug("Files listed successfully", {
|
|
1068
|
+
path,
|
|
1069
|
+
count: entries.length,
|
|
1070
|
+
duration: `${duration}ms`,
|
|
1071
|
+
method: "hybrid"
|
|
1072
|
+
});
|
|
1073
|
+
return entries;
|
|
1074
|
+
} catch (error) {
|
|
1075
|
+
logger.warn("Shell ls command failed, falling back to SFTP readdir", {
|
|
1076
|
+
path,
|
|
1077
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1078
|
+
});
|
|
1079
|
+
return this.listFilesViaSFTP(path, includeHidden);
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
/**
|
|
1083
|
+
* Fallback method: List files using SFTP readdir (may miss hidden files)
|
|
1084
|
+
* @private
|
|
1085
|
+
*/
|
|
1086
|
+
async listFilesViaSFTP(path, includeHidden) {
|
|
961
1087
|
const sftp = await this.getSFTP();
|
|
962
1088
|
return new Promise((resolve, reject) => {
|
|
963
1089
|
sftp.readdir(path, (err, list) => {
|
|
964
1090
|
if (err) {
|
|
1091
|
+
logger.error("SFTP readdir failed", { path, error: err.message });
|
|
965
1092
|
reject(err);
|
|
966
1093
|
return;
|
|
967
1094
|
}
|
|
968
|
-
|
|
1095
|
+
let entries = list.map((item) => ({
|
|
969
1096
|
name: item.filename,
|
|
970
|
-
|
|
1097
|
+
path: `${path}/${item.filename}`.replace(/\/+/g, "/"),
|
|
1098
|
+
type: getFileType(item.attrs),
|
|
1099
|
+
size: item.attrs.size,
|
|
1100
|
+
permissions: parsePermissions(item.attrs.mode),
|
|
1101
|
+
owner: {
|
|
1102
|
+
uid: item.attrs.uid,
|
|
1103
|
+
gid: item.attrs.gid
|
|
1104
|
+
},
|
|
1105
|
+
timestamps: {
|
|
1106
|
+
accessed: new Date(item.attrs.atime * 1e3).toISOString(),
|
|
1107
|
+
modified: new Date(item.attrs.mtime * 1e3).toISOString()
|
|
1108
|
+
}
|
|
971
1109
|
}));
|
|
972
|
-
|
|
1110
|
+
if (!includeHidden) {
|
|
1111
|
+
entries = entries.filter((e) => !e.name.startsWith("."));
|
|
1112
|
+
}
|
|
1113
|
+
logger.debug("Files listed via SFTP fallback", {
|
|
1114
|
+
path,
|
|
1115
|
+
count: entries.length,
|
|
1116
|
+
method: "sftp",
|
|
1117
|
+
note: "Hidden files may be missing (SFTP limitation)"
|
|
1118
|
+
});
|
|
1119
|
+
resolve(entries);
|
|
973
1120
|
});
|
|
974
1121
|
});
|
|
975
1122
|
}
|