@ebowwa/terminal 0.2.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/dist/client.d.ts +15 -0
- package/dist/client.js +45 -0
- package/dist/error.d.ts +8 -0
- package/dist/error.js +12 -0
- package/dist/exec.d.ts +47 -0
- package/dist/exec.js +107 -0
- package/dist/files.d.ts +124 -0
- package/dist/files.js +436 -0
- package/dist/fingerprint.d.ts +67 -0
- package/dist/index.d.ts +17 -0
- package/dist/pool.d.ts +143 -0
- package/dist/pool.js +554 -0
- package/dist/pty.d.ts +59 -0
- package/dist/scp.d.ts +30 -0
- package/dist/scp.js +74 -0
- package/dist/sessions.d.ts +98 -0
- package/dist/tmux-exec.d.ts +50 -0
- package/dist/tmux.d.ts +213 -0
- package/dist/tmux.js +528 -0
- package/dist/types.d.ts +18 -0
- package/dist/types.js +5 -0
- package/ebowwa-terminal-0.2.0.tgz +0 -0
- package/mcp/README.md +181 -0
- package/mcp/package.json +34 -0
- package/mcp/test-fix.sh +273 -0
- package/package.json +118 -0
- package/src/api.ts +752 -0
- package/src/client.ts +55 -0
- package/src/config.ts +489 -0
- package/src/error.ts +13 -0
- package/src/exec.ts +128 -0
- package/src/files.ts +636 -0
- package/src/fingerprint.ts +263 -0
- package/src/index.ts +144 -0
- package/src/manager.ts +319 -0
- package/src/mcp/index.ts +467 -0
- package/src/mcp/stdio.ts +708 -0
- package/src/network-error-detector.ts +121 -0
- package/src/pool.ts +662 -0
- package/src/pty.ts +285 -0
- package/src/scp.ts +109 -0
- package/src/sessions.ts +861 -0
- package/src/tmux-exec.ts +96 -0
- package/src/tmux-local.ts +839 -0
- package/src/tmux-manager.ts +962 -0
- package/src/tmux.ts +711 -0
- package/src/types.ts +19 -0
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core SSH client for executing commands on remote servers
|
|
3
|
+
* Uses persistent connection pool for efficient reuse
|
|
4
|
+
* Uses base64 encoding to avoid shell escaping issues
|
|
5
|
+
*/
|
|
6
|
+
import type { SSHOptions } from "./types.js";
|
|
7
|
+
/**
|
|
8
|
+
* Execute a command on a remote server via SSH
|
|
9
|
+
* Uses persistent connection pool for better performance
|
|
10
|
+
* @param command - Shell command to execute
|
|
11
|
+
* @param options - SSH connection options
|
|
12
|
+
* @returns Command output as string
|
|
13
|
+
*/
|
|
14
|
+
export declare function execSSH(command: string, options: SSHOptions): Promise<string>;
|
|
15
|
+
//# sourceMappingURL=client.d.ts.map
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core SSH client for executing commands on remote servers
|
|
3
|
+
* Uses persistent connection pool for efficient reuse
|
|
4
|
+
* Uses base64 encoding to avoid shell escaping issues
|
|
5
|
+
*/
|
|
6
|
+
import { SSHError } from "./error.js";
|
|
7
|
+
import { SSHOptionsSchema, SSHCommandSchema } from '@ebowwa/codespaces-types/runtime/ssh';
|
|
8
|
+
import { getSSHPool } from './pool.js';
|
|
9
|
+
/**
|
|
10
|
+
* Execute a command on a remote server via SSH
|
|
11
|
+
* Uses persistent connection pool for better performance
|
|
12
|
+
* @param command - Shell command to execute
|
|
13
|
+
* @param options - SSH connection options
|
|
14
|
+
* @returns Command output as string
|
|
15
|
+
*/
|
|
16
|
+
export async function execSSH(command, options) {
|
|
17
|
+
// Validate inputs with Zod
|
|
18
|
+
const validatedCommand = SSHCommandSchema.safeParse(command);
|
|
19
|
+
if (!validatedCommand.success) {
|
|
20
|
+
throw new Error(`Invalid SSH command: ${validatedCommand.error.issues.map(i => i.message).join(', ')}`);
|
|
21
|
+
}
|
|
22
|
+
const validatedOptions = SSHOptionsSchema.safeParse(options);
|
|
23
|
+
if (!validatedOptions.success) {
|
|
24
|
+
throw new Error(`Invalid SSH options: ${validatedOptions.error.issues.map(i => i.message).join(', ')}`);
|
|
25
|
+
}
|
|
26
|
+
const { host, user = "root", timeout = 5, port = 22, keyPath, password } = validatedOptions.data;
|
|
27
|
+
try {
|
|
28
|
+
// Get connection pool
|
|
29
|
+
const pool = getSSHPool();
|
|
30
|
+
// Execute command directly via SSH - the pool handles proper escaping
|
|
31
|
+
const output = await pool.exec(validatedCommand.data, {
|
|
32
|
+
host,
|
|
33
|
+
user,
|
|
34
|
+
timeout,
|
|
35
|
+
port,
|
|
36
|
+
keyPath,
|
|
37
|
+
password,
|
|
38
|
+
});
|
|
39
|
+
return output || "0";
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
throw new SSHError(`SSH command failed: ${validatedCommand.data}`, error);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=client.js.map
|
package/dist/error.d.ts
ADDED
package/dist/error.js
ADDED
package/dist/exec.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SSH command execution functions
|
|
3
|
+
*/
|
|
4
|
+
import type { SSHOptions } from "./types.js";
|
|
5
|
+
/**
|
|
6
|
+
* Execute multiple SSH commands in parallel using multiple connections
|
|
7
|
+
*
|
|
8
|
+
* DESIGN DECISION: Multiple Connections vs Single Connection
|
|
9
|
+
* ===========================================================
|
|
10
|
+
*
|
|
11
|
+
* We use MULTIPLE SSH connections to avoid channel saturation issues.
|
|
12
|
+
* SSH servers typically limit concurrent channels per connection (~10).
|
|
13
|
+
* When executing 9+ commands in parallel, we can exceed this limit.
|
|
14
|
+
*
|
|
15
|
+
* Solution: Distribute commands across multiple pooled connections.
|
|
16
|
+
* Each connection handles a subset of commands, staying within channel limits.
|
|
17
|
+
*
|
|
18
|
+
* DESIGN DECISION: Promise.allSettled() vs Promise.all()
|
|
19
|
+
* ======================================================
|
|
20
|
+
*
|
|
21
|
+
* We use Promise.allSettled() instead of Promise.all() for a critical reason:
|
|
22
|
+
* Resource monitoring should be RESILIENT. If one command fails (e.g., GPU
|
|
23
|
+
* query on a CPU-only server), we still want results from all other commands.
|
|
24
|
+
*
|
|
25
|
+
* Example scenario:
|
|
26
|
+
* - CPU, memory, disk commands: succeed
|
|
27
|
+
* - GPU command: fails (no NVIDIA GPU)
|
|
28
|
+
* - Network command: succeeds
|
|
29
|
+
*
|
|
30
|
+
* With Promise.all(): entire batch fails, no metrics collected
|
|
31
|
+
* With Promise.allSettled(): we get 6/7 metrics, GPU returns "0" fallback
|
|
32
|
+
*
|
|
33
|
+
* ERROR HANDLING:
|
|
34
|
+
* ==============
|
|
35
|
+
* 1. Individual command failures are logged to console
|
|
36
|
+
* 2. Failed commands return "0" as fallback (matches execSSH default)
|
|
37
|
+
* 3. The function always completes successfully (never throws)
|
|
38
|
+
* 4. Calling code can check for "0" values to detect failures
|
|
39
|
+
*/
|
|
40
|
+
export declare function execSSHParallel(commands: Record<string, string>, options: SSHOptions): Promise<Record<string, string>>;
|
|
41
|
+
/**
|
|
42
|
+
* Test SSH connection to a remote server
|
|
43
|
+
* @param options - SSH connection options
|
|
44
|
+
* @returns True if connection successful
|
|
45
|
+
*/
|
|
46
|
+
export declare function testSSHConnection(options: SSHOptions): Promise<boolean>;
|
|
47
|
+
//# sourceMappingURL=exec.d.ts.map
|
package/dist/exec.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SSH command execution functions
|
|
3
|
+
*/
|
|
4
|
+
import { execSSH } from "./client.js";
|
|
5
|
+
import { getSSHPool } from "./pool.js";
|
|
6
|
+
/**
|
|
7
|
+
* Execute multiple SSH commands in parallel using multiple connections
|
|
8
|
+
*
|
|
9
|
+
* DESIGN DECISION: Multiple Connections vs Single Connection
|
|
10
|
+
* ===========================================================
|
|
11
|
+
*
|
|
12
|
+
* We use MULTIPLE SSH connections to avoid channel saturation issues.
|
|
13
|
+
* SSH servers typically limit concurrent channels per connection (~10).
|
|
14
|
+
* When executing 9+ commands in parallel, we can exceed this limit.
|
|
15
|
+
*
|
|
16
|
+
* Solution: Distribute commands across multiple pooled connections.
|
|
17
|
+
* Each connection handles a subset of commands, staying within channel limits.
|
|
18
|
+
*
|
|
19
|
+
* DESIGN DECISION: Promise.allSettled() vs Promise.all()
|
|
20
|
+
* ======================================================
|
|
21
|
+
*
|
|
22
|
+
* We use Promise.allSettled() instead of Promise.all() for a critical reason:
|
|
23
|
+
* Resource monitoring should be RESILIENT. If one command fails (e.g., GPU
|
|
24
|
+
* query on a CPU-only server), we still want results from all other commands.
|
|
25
|
+
*
|
|
26
|
+
* Example scenario:
|
|
27
|
+
* - CPU, memory, disk commands: succeed
|
|
28
|
+
* - GPU command: fails (no NVIDIA GPU)
|
|
29
|
+
* - Network command: succeeds
|
|
30
|
+
*
|
|
31
|
+
* With Promise.all(): entire batch fails, no metrics collected
|
|
32
|
+
* With Promise.allSettled(): we get 6/7 metrics, GPU returns "0" fallback
|
|
33
|
+
*
|
|
34
|
+
* ERROR HANDLING:
|
|
35
|
+
* ==============
|
|
36
|
+
* 1. Individual command failures are logged to console
|
|
37
|
+
* 2. Failed commands return "0" as fallback (matches execSSH default)
|
|
38
|
+
* 3. The function always completes successfully (never throws)
|
|
39
|
+
* 4. Calling code can check for "0" values to detect failures
|
|
40
|
+
*/
|
|
41
|
+
export async function execSSHParallel(commands, options) {
|
|
42
|
+
const entries = Object.entries(commands);
|
|
43
|
+
const pool = getSSHPool();
|
|
44
|
+
// Determine optimal number of connections (3-4 connections is a good balance)
|
|
45
|
+
// This avoids SSH channel limits while maintaining parallelism
|
|
46
|
+
const numCommands = entries.length;
|
|
47
|
+
const numConnections = Math.min(numCommands, 4); // Max 4 connections
|
|
48
|
+
// Get multiple connections from the pool
|
|
49
|
+
const connections = await pool.getConnections(options, numConnections);
|
|
50
|
+
// Distribute commands across connections (round-robin)
|
|
51
|
+
const connectionPromises = connections.map((ssh, connIndex) => {
|
|
52
|
+
// Assign commands to this connection
|
|
53
|
+
const assignedCommands = entries.filter((_, i) => i % numConnections === connIndex);
|
|
54
|
+
// Execute all assigned commands on this connection
|
|
55
|
+
return Promise.allSettled(assignedCommands.map(async ([key, cmd]) => {
|
|
56
|
+
try {
|
|
57
|
+
const result = await ssh.execCommand(cmd, {
|
|
58
|
+
execOptions: {
|
|
59
|
+
timeout: (options.timeout || 5) * 1000,
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
// If we have stderr but no stdout, the command failed
|
|
63
|
+
if (result.stderr && !result.stdout) {
|
|
64
|
+
throw new Error(result.stderr);
|
|
65
|
+
}
|
|
66
|
+
return [key, result.stdout.trim()];
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
// Log the error with full details including cause
|
|
70
|
+
console.error(`[execSSHParallel] Command "${key}" failed:`, error instanceof Error ? error.message : error);
|
|
71
|
+
// Log the underlying cause if available
|
|
72
|
+
if (error instanceof Error && error.cause) {
|
|
73
|
+
console.error(`[execSSHParallel] Command "${key}" cause:`, error.cause);
|
|
74
|
+
}
|
|
75
|
+
return [key, "0"]; // Fallback value
|
|
76
|
+
}
|
|
77
|
+
}));
|
|
78
|
+
});
|
|
79
|
+
// Wait for all connections to complete their assigned commands
|
|
80
|
+
const allSettledResults = await Promise.all(connectionPromises);
|
|
81
|
+
// Flatten results from all connections
|
|
82
|
+
const results = [];
|
|
83
|
+
for (const connResults of allSettledResults) {
|
|
84
|
+
for (const result of connResults) {
|
|
85
|
+
if (result.status === "fulfilled") {
|
|
86
|
+
results.push([...result.value]);
|
|
87
|
+
}
|
|
88
|
+
// Rejected promises are already logged and return "0" above
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return Object.fromEntries(results);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Test SSH connection to a remote server
|
|
95
|
+
* @param options - SSH connection options
|
|
96
|
+
* @returns True if connection successful
|
|
97
|
+
*/
|
|
98
|
+
export async function testSSHConnection(options) {
|
|
99
|
+
try {
|
|
100
|
+
await execSSH('echo "connection_test"', options);
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=exec.js.map
|
package/dist/files.d.ts
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remote file operations via SSH
|
|
3
|
+
*/
|
|
4
|
+
import type { SSHOptions } from "./types.js";
|
|
5
|
+
import { SSHError } from "./error.js";
|
|
6
|
+
/**
|
|
7
|
+
* Path sanitization options
|
|
8
|
+
*/
|
|
9
|
+
export interface SanitizePathOptions {
|
|
10
|
+
/**
|
|
11
|
+
* Allowed base directories (absolute paths)
|
|
12
|
+
* Default: ["/root"] for root user
|
|
13
|
+
*/
|
|
14
|
+
allowedBaseDirs?: string[];
|
|
15
|
+
/**
|
|
16
|
+
* User context for determining default base directory
|
|
17
|
+
*/
|
|
18
|
+
user?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Whether to allow absolute paths
|
|
21
|
+
* Default: false (security best practice)
|
|
22
|
+
*/
|
|
23
|
+
allowAbsolutePaths?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Maximum path depth to prevent deep traversal attempts
|
|
26
|
+
* Default: 20
|
|
27
|
+
*/
|
|
28
|
+
maxDepth?: number;
|
|
29
|
+
/**
|
|
30
|
+
* Log suspicious path attempts
|
|
31
|
+
* Default: true
|
|
32
|
+
*/
|
|
33
|
+
logSuspicious?: boolean;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Path traversal security error
|
|
37
|
+
*/
|
|
38
|
+
export declare class PathTraversalError extends SSHError {
|
|
39
|
+
readonly attemptedPath: string;
|
|
40
|
+
readonly reason: string;
|
|
41
|
+
constructor(message: string, attemptedPath: string, reason: string);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Security event log for path traversal attempts
|
|
45
|
+
*/
|
|
46
|
+
interface SecurityEvent {
|
|
47
|
+
timestamp: string;
|
|
48
|
+
attemptedPath: string;
|
|
49
|
+
reason: string;
|
|
50
|
+
severity: "blocked" | "suspicious" | "warning";
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Get recent security events for monitoring
|
|
54
|
+
*/
|
|
55
|
+
export declare function getSecurityEvents(limit?: number): SecurityEvent[];
|
|
56
|
+
/**
|
|
57
|
+
* Clear old security events (for maintenance)
|
|
58
|
+
*/
|
|
59
|
+
export declare function clearSecurityEvents(olderThanMs?: number): number;
|
|
60
|
+
/**
|
|
61
|
+
* Sanitize and validate a file path for security
|
|
62
|
+
*
|
|
63
|
+
* This function prevents path traversal attacks by:
|
|
64
|
+
* 1. Rejecting paths with .. components
|
|
65
|
+
* 2. Validating against allowed base directories
|
|
66
|
+
* 3. Normalizing paths to remove . and redundant /
|
|
67
|
+
* 4. Checking for null bytes and other escape sequences
|
|
68
|
+
* 5. Limiting path depth to prevent deep traversal
|
|
69
|
+
*
|
|
70
|
+
* @param inputPath - The user-provided path to sanitize
|
|
71
|
+
* @param options - Sanitization options
|
|
72
|
+
* @returns Sanitized absolute path
|
|
73
|
+
* @throws PathTraversalError if path is suspicious or invalid
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```ts
|
|
77
|
+
* // Safe: within /root
|
|
78
|
+
* sanitizePath("project/file.txt", { user: "root" })
|
|
79
|
+
* // Returns: "/root/project/file.txt"
|
|
80
|
+
*
|
|
81
|
+
* // BLOCKED: attempts to escape
|
|
82
|
+
* sanitizePath("../../../etc/passwd", { user: "root" })
|
|
83
|
+
* // Throws: PathTraversalError
|
|
84
|
+
*
|
|
85
|
+
* // BLOCKED: null byte injection
|
|
86
|
+
* sanitizePath("file.txt\0../../../etc/passwd", { user: "root" })
|
|
87
|
+
* // Throws: PathTraversalError
|
|
88
|
+
* ```
|
|
89
|
+
*/
|
|
90
|
+
export declare function sanitizePath(inputPath: string, options?: SanitizePathOptions): string;
|
|
91
|
+
export type FileType = "file" | "directory";
|
|
92
|
+
export interface RemoteFile {
|
|
93
|
+
name: string;
|
|
94
|
+
path: string;
|
|
95
|
+
size: string;
|
|
96
|
+
modified: string;
|
|
97
|
+
type: FileType;
|
|
98
|
+
}
|
|
99
|
+
export type PreviewType = "text" | "image" | "binary" | "error";
|
|
100
|
+
export interface FilePreview {
|
|
101
|
+
type: PreviewType;
|
|
102
|
+
content?: string;
|
|
103
|
+
error?: string;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* List files in a directory on remote server
|
|
107
|
+
* @param path - Directory path to list (default: .)
|
|
108
|
+
* @param options - SSH connection options
|
|
109
|
+
* @returns List of files with metadata
|
|
110
|
+
* @throws PathTraversalError if path attempts to escape allowed directories
|
|
111
|
+
* @throws SSHError if SSH command fails
|
|
112
|
+
*/
|
|
113
|
+
export declare function listFiles(path: string | undefined, options: SSHOptions): Promise<RemoteFile[]>;
|
|
114
|
+
/**
|
|
115
|
+
* Preview a file's content from remote server
|
|
116
|
+
* @param filePath - Path to the file to preview
|
|
117
|
+
* @param options - SSH connection options
|
|
118
|
+
* @returns File content for preview
|
|
119
|
+
* @throws PathTraversalError if path attempts to escape allowed directories
|
|
120
|
+
* @throws SSHError if SSH command fails
|
|
121
|
+
*/
|
|
122
|
+
export declare function previewFile(filePath: string, options: SSHOptions): Promise<FilePreview>;
|
|
123
|
+
export {};
|
|
124
|
+
//# sourceMappingURL=files.d.ts.map
|