@light-merlin-dark/vssh
Version:
MCP-native SSH proxy for AI agents. CLI & MCP Server, plugin system, AI safety guards.
167 lines ⢠6.84 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.CONFIG_PATH = exports.LOGS_PATH = exports.DATA_PATH = exports.PROJECT_PATH = void 0;
exports.saveConfig = saveConfig;
exports.loadConfig = loadConfig;
exports.setupInteractiveConfig = setupInteractiveConfig;
const zod_1 = require("zod");
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const os = __importStar(require("os"));
const readline = __importStar(require("readline"));
const crypto = __importStar(require("crypto"));
// Dynamic paths based on user's home directory
exports.PROJECT_PATH = path.join(os.homedir(), '.vssh');
exports.DATA_PATH = path.join(exports.PROJECT_PATH, 'data');
exports.LOGS_PATH = path.join(exports.DATA_PATH, 'logs');
exports.CONFIG_PATH = path.join(exports.PROJECT_PATH, 'config.json');
const envSchema = zod_1.z.object({
VSSH_HOST: zod_1.z.string().optional(),
VSSH_USER: zod_1.z.string().optional().default('root'),
VSSH_KEY_PATH: zod_1.z.string().optional().default(`${process.env.HOME}/.ssh/id_rsa`)
});
const configSchema = zod_1.z.object({
host: zod_1.z.string(),
user: zod_1.z.string(),
keyPath: zod_1.z.string(),
localMode: zod_1.z.boolean().optional().default(false),
encryptionKey: zod_1.z.string().optional(),
plugins: zod_1.z.object({
enabled: zod_1.z.array(zod_1.z.string()).optional(),
disabled: zod_1.z.array(zod_1.z.string()).optional(),
config: zod_1.z.record(zod_1.z.any()).optional()
}).optional()
});
function saveConfig(config) {
fs.mkdirSync(exports.PROJECT_PATH, { recursive: true });
fs.writeFileSync(exports.CONFIG_PATH, JSON.stringify(config, null, 2));
}
function loadConfig() {
// Ensure data directories exist
fs.mkdirSync(exports.LOGS_PATH, { recursive: true });
// First try to load from saved config file
if (fs.existsSync(exports.CONFIG_PATH)) {
try {
const savedConfig = JSON.parse(fs.readFileSync(exports.CONFIG_PATH, 'utf-8'));
return configSchema.parse(savedConfig);
}
catch (error) {
console.error('ā ļø Warning: Failed to parse saved config:', error);
}
}
// Fall back to environment variables
const envConfig = envSchema.safeParse({
VSSH_HOST: process.env.VSSH_HOST || process.env.SSH_HOST,
VSSH_USER: process.env.VSSH_USER,
VSSH_KEY_PATH: process.env.VSSH_KEY_PATH
});
if (envConfig.success && envConfig.data.VSSH_HOST) {
return {
host: envConfig.data.VSSH_HOST,
user: envConfig.data.VSSH_USER,
keyPath: envConfig.data.VSSH_KEY_PATH,
plugins: {
enabled: ['file-transfer', 'docker', 'coolify'], // Default enabled plugins
disabled: ['grafana'] // Grafana requires manual configuration
}
};
}
// No config found
return null;
}
async function setupInteractiveConfig() {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
const question = (prompt) => {
return new Promise((resolve) => {
rl.question(prompt, resolve);
});
};
console.log('\nš§ First-time setup for vssh\n');
console.log('vssh is an AI-friendly SSH command proxy that helps AI assistants');
console.log('safely execute commands on remote servers with built-in safety guards.\n');
// Get SSH host
const host = await question('SSH host (e.g., example.com or 192.168.1.100): ');
if (!host) {
console.error('\nā Host is required');
process.exit(1);
}
// Get SSH user
const user = await question(`SSH user [${process.env.USER || 'root'}]: `) || process.env.USER || 'root';
// Detect SSH keys
const defaultKeyPath = path.join(os.homedir(), '.ssh', 'id_rsa');
const alternativeKeys = ['id_ed25519', 'id_ecdsa', 'id_dsa'];
let suggestedKey = defaultKeyPath;
if (!fs.existsSync(defaultKeyPath)) {
for (const keyName of alternativeKeys) {
const keyPath = path.join(os.homedir(), '.ssh', keyName);
if (fs.existsSync(keyPath)) {
suggestedKey = keyPath;
break;
}
}
}
const keyPath = await question(`SSH private key path [${suggestedKey}]: `) || suggestedKey;
if (!fs.existsSync(keyPath)) {
console.error(`\nā SSH key not found at: ${keyPath}`);
console.error('Please ensure you have an SSH key set up.');
process.exit(1);
}
rl.close();
// Generate encryption key
const encryptionKey = crypto.randomBytes(32).toString('base64');
const config = {
host,
user,
keyPath,
encryptionKey,
plugins: {
enabled: ['file-transfer', 'docker', 'coolify'], // Default enabled plugins
disabled: ['grafana'] // Grafana requires manual configuration
}
};
// Save config
fs.mkdirSync(exports.PROJECT_PATH, { recursive: true });
fs.writeFileSync(exports.CONFIG_PATH, JSON.stringify(config, null, 2));
console.log(`\nā
Configuration saved to: ${exports.CONFIG_PATH}`);
console.log('š Encryption key generated for secure credential storage');
console.log('\nYou can now use vssh to execute commands on your remote server.');
console.log('Example: vssh ls -la\n');
return config;
}
//# sourceMappingURL=config.js.map