@ebowwa/terminal 0.2.1 → 0.3.1

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/api.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ /**
2
+ * API routes for Multi-Node Tmux Session Manager
3
+ * Provides REST API for managing tmux sessions across multiple VPS nodes
4
+ */
5
+ import { Hono } from "hono";
6
+ declare const tmuxApi: Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;
7
+ export { tmuxApi };
@@ -0,0 +1,14 @@
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>;
@@ -0,0 +1,85 @@
1
+ /**
2
+ * SSH Config Manager
3
+ *
4
+ * Manages ~/.ssh/config entries for easy node access.
5
+ * When a node is created, adds an alias so you can:
6
+ * ssh node-<id> or ssh <name>
7
+ * Instead of:
8
+ * ssh -i ~/.../key -o StrictHostKeyChecking=no root@167.235.236.8
9
+ */
10
+ export interface SSHConfigEntry {
11
+ id: string;
12
+ name: string;
13
+ host: string;
14
+ user?: string;
15
+ keyPath: string;
16
+ port?: number;
17
+ }
18
+ /**
19
+ * Add or update an SSH config entry for a node
20
+ */
21
+ export declare function addSSHConfigEntry(entry: SSHConfigEntry): void;
22
+ /**
23
+ * Remove an SSH config entry for a node
24
+ */
25
+ export declare function removeSSHConfigEntry(id: string): void;
26
+ /**
27
+ * Update IP address for an existing node (e.g., after rebuild)
28
+ */
29
+ export declare function updateSSHConfigHost(id: string, newHost: string): void;
30
+ /**
31
+ * List all managed SSH config entries
32
+ */
33
+ export declare function listSSHConfigEntries(): SSHConfigEntry[];
34
+ /**
35
+ * Validate SSH connection works with the configured key
36
+ * Returns true if connection succeeds, throws on failure with diagnostic info
37
+ */
38
+ export declare function validateSSHConnection(host: string, keyPath: string, user?: string, timeoutSeconds?: number): Promise<{
39
+ success: boolean;
40
+ error?: string;
41
+ diagnostics?: string;
42
+ }>;
43
+ /**
44
+ * Ensure SSH key is loaded correctly and agent doesn't interfere
45
+ * Clears wrong keys from agent and adds the correct one
46
+ */
47
+ export declare function ensureCorrectSSHKey(keyPath: string): Promise<void>;
48
+ /**
49
+ * Wait for SSH to become ready on a new server
50
+ * Polls until connection succeeds or timeout
51
+ */
52
+ export declare function waitForSSHReady(host: string, keyPath: string, options?: {
53
+ user?: string;
54
+ maxAttempts?: number;
55
+ intervalMs?: number;
56
+ onAttempt?: (attempt: number, maxAttempts: number) => void;
57
+ }): Promise<{
58
+ success: boolean;
59
+ attempts: number;
60
+ error?: string;
61
+ }>;
62
+ /**
63
+ * Sync result for a single node
64
+ */
65
+ export interface SyncResult {
66
+ id: string;
67
+ name: string;
68
+ ip: string;
69
+ status: "added" | "updated" | "skipped" | "error";
70
+ error?: string;
71
+ sshReady?: boolean;
72
+ }
73
+ /**
74
+ * Sync all existing Hetzner nodes to SSH config
75
+ * Call this to add aliases for nodes created before this feature
76
+ */
77
+ export declare function syncNodesToSSHConfig(nodes: Array<{
78
+ id: string;
79
+ name: string;
80
+ ip: string;
81
+ keyPath: string;
82
+ }>, options?: {
83
+ validateSSH?: boolean;
84
+ onProgress?: (result: SyncResult) => void;
85
+ }): Promise<SyncResult[]>;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * SSH error class
3
+ */
4
+ export declare class SSHError extends Error {
5
+ readonly cause?: unknown;
6
+ constructor(message: string, cause?: unknown);
7
+ }
package/dist/exec.d.ts ADDED
@@ -0,0 +1,46 @@
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>;
@@ -0,0 +1,123 @@
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, 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 {};
@@ -0,0 +1,66 @@
1
+ /**
2
+ * SSH fingerprint utilities with validation and recovery
3
+ */
4
+ import type { SSHOptions } from "./types.js";
5
+ /**
6
+ * Get SSH fingerprint from remote server
7
+ * @param options - SSH connection options
8
+ * @returns SSH fingerprint or null
9
+ */
10
+ export declare function getSSHFingerprint(options: SSHOptions): Promise<string | null>;
11
+ /**
12
+ * Get SSH fingerprint from a local private key file
13
+ * @param keyPath - Path to the private key file
14
+ * @returns SSH fingerprint (SHA256 format) or null
15
+ */
16
+ export declare function getLocalKeyFingerprint(keyPath: string): Promise<string | null>;
17
+ /**
18
+ * Convert MD5 fingerprint format to SHA256 format (for comparison)
19
+ * Hetzner returns MD5 like "29:cd:c1:c3:84:eb:ca:31:a4:1f:94:69:0c:84:b3:56"
20
+ * We need to handle both formats
21
+ */
22
+ export declare function normalizeFingerprint(fingerprint: string): string;
23
+ /**
24
+ * Validate that a local SSH key matches what's on a remote server
25
+ * @param host - Server hostname or IP
26
+ * @param keyPath - Path to local private key
27
+ * @returns Validation result
28
+ */
29
+ export declare function validateSSHKeyMatch(host: string, keyPath: string): Promise<{
30
+ valid: boolean;
31
+ localFingerprint?: string;
32
+ remoteFingerprint?: string;
33
+ error?: string;
34
+ }>;
35
+ /**
36
+ * Check if we can SSH to a server with a given key
37
+ * @param host - Server hostname or IP
38
+ * @param keyPath - Path to SSH private key
39
+ * @returns true if SSH works
40
+ */
41
+ export declare function testSSHKeyConnection(host: string, keyPath: string): Promise<boolean>;
42
+ /**
43
+ * SSH Key Mismatch Error with recovery suggestions
44
+ */
45
+ export declare class SSHKeyMismatchError extends Error {
46
+ host: string;
47
+ localFingerprint: string;
48
+ hetznerFingerprint: string;
49
+ keyPath: string;
50
+ constructor(host: string, localFingerprint: string, hetznerFingerprint: string, keyPath: string);
51
+ }
52
+ /**
53
+ * Comprehensive SSH key validation for server creation
54
+ * @param host - Server hostname or IP
55
+ * @param keyPath - Path to local SSH key
56
+ * @param hetznerKeyId - SSH key ID on Hetzner (for comparison)
57
+ * @returns Validation result with recovery suggestions
58
+ */
59
+ export declare function validateSSHKeyForServer(host: string, keyPath: string, hetznerKeyId?: string): Promise<{
60
+ canConnect: boolean;
61
+ fingerprintMatch: boolean;
62
+ localFingerprint?: string;
63
+ remoteFingerprint?: string;
64
+ error?: string;
65
+ recovery?: string[];
66
+ }>;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * SSH utility library - modular entry point
3
+ */
4
+ export { TmuxSessionManager, getTmuxManager, resetTmuxManager, type Node, type TmuxSession, type DetailedTmuxSession, type BatchOperationResult, type CreateSessionOptions, type BatchCommandOptions, type SessionQueryOptions, } from "./tmux-manager.js";
5
+ export type { SSHOptions, SCPOptions } from "./types.js";
6
+ export { SSHError } from "./error.js";
7
+ export { execSSH } from "./client.js";
8
+ export { execSSHParallel, testSSHConnection } from "./exec.js";
9
+ export { execViaTmux, execViaTmuxParallel } from "./tmux-exec.js";
10
+ export { scpUpload, scpDownload } from "./scp.js";
11
+ export { listFiles, previewFile, sanitizePath, PathTraversalError, getSecurityEvents, clearSecurityEvents, type FileType, type RemoteFile, type PreviewType, type FilePreview, type SanitizePathOptions, } from "./files.js";
12
+ export { getSSHFingerprint, getLocalKeyFingerprint, normalizeFingerprint, validateSSHKeyMatch, testSSHKeyConnection, validateSSHKeyForServer, SSHKeyMismatchError } from "./fingerprint.js";
13
+ export { createPTYSession, writeToPTY, setPTYSize, readFromPTY, closePTYSession, getPTYSession, getActivePTYSessions, } from "./pty.js";
14
+ export { getSSHPool, closeGlobalSSHPool, getActiveSSHConnections, SSHConnectionPool, } from "./pool.js";
15
+ export { closeSession, cleanupStaleSessions, getOrCreateSession, getSession, getAllSessions, getAllSessionInfo, getSessionInfo, getSessionCount, getSessionsByHost, attachWebSocket, writeToSession, resizeSession, detachWebSocket, } from "./sessions.js";
16
+ export type { TerminalSession, SessionInfo } from "./sessions.js";
17
+ export { generateSessionName, isTmuxInstalled, installTmux, ensureTmux, listTmuxSessions, hasTmuxSession, createOrAttachTmuxSession, killTmuxSession, getTmuxSessionInfo, cleanupOldTmuxSessions, getTmuxResourceUsage, sendCommandToPane, splitPane, listSessionWindows, listWindowPanes, capturePane, getPaneHistory, switchWindow, switchPane, renameWindow, killPane, getDetailedSessionInfo, } from "./tmux.js";
18
+ export { generateLocalSessionName, isLocalTmuxInstalled, listLocalSessions, hasLocalSession, createLocalTmuxSSHSession, sendCommandToLocalSession, captureLocalPane, getLocalPaneHistory, killLocalSession, getLocalSessionInfo, listLocalSessionWindows, listLocalWindowPanes, splitLocalPane, cleanupOldLocalSessions, getLocalTmuxResourceUsage, waitForTextInPane, switchLocalWindow, switchLocalPane, renameLocalWindow, killLocalPane, getDetailedLocalSessionInfo, type LocalTmuxSessionOptions, type LocalTmuxSessionResult, } from "./tmux-local.js";
19
+ export { RESOURCE_COMMANDS } from "./resources.js";
20
+ export type { ResourceCommand } from "./resources.js";