beeline-cli
Version:
A terminal wallet for the Hive blockchain - type, sign, rule the chain
395 lines • 17.9 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.SimplePluginManager = void 0;
exports.getPluginManager = getPluginManager;
const fs = __importStar(require("fs-extra"));
const path = __importStar(require("path"));
const neon_js_1 = require("./neon.js");
// Simple plugin manager
class SimplePluginManager {
constructor() {
this.plugins = new Map();
this.registeredCommands = new Map();
this.pluginDir = path.join(process.env.HOME || '', '.beeline', 'plugins');
}
async initialize() {
await fs.ensureDir(this.pluginDir);
await this.loadAllPlugins();
}
// Install a plugin from a local directory
async installPlugin(sourcePath) {
const theme = await (0, neon_js_1.getTheme)();
try {
// Resolve source path
const resolvedSource = path.resolve(sourcePath);
// Check if source exists and has package.json
const packagePath = path.join(resolvedSource, 'package.json');
if (!await fs.pathExists(packagePath)) {
throw new Error('No package.json found in plugin directory');
}
const packageJson = await fs.readJson(packagePath);
const pluginName = packageJson.name;
if (!pluginName) {
throw new Error('Plugin package.json must have a name field');
}
// Copy plugin to plugins directory
const targetPath = path.join(this.pluginDir, pluginName);
if (await fs.pathExists(targetPath)) {
console.log(theme.chalk.warning(`${neon_js_1.neonSymbols.warning} Plugin ${pluginName} already exists, updating...`));
await fs.remove(targetPath);
}
await fs.copy(resolvedSource, targetPath);
// Load the plugin
await this.loadPlugin(targetPath);
console.log(theme.chalk.success(`${neon_js_1.neonSymbols.check} Plugin installed: ${pluginName}`));
}
catch (error) {
console.log(theme.chalk.error(`${neon_js_1.neonSymbols.cross} Failed to install plugin: ${error instanceof Error ? error.message : 'Unknown error'}`));
throw error;
}
}
// Load all plugins from plugin directory with better isolation
async loadAllPlugins() {
try {
const entries = await fs.readdir(this.pluginDir);
for (const entry of entries) {
const pluginPath = path.join(this.pluginDir, entry);
const stat = await fs.stat(pluginPath);
if (stat.isDirectory()) {
try {
// Skip if plugin is already loaded to prevent conflicts
const packagePath = path.join(pluginPath, 'package.json');
if (await fs.pathExists(packagePath)) {
const packageJson = await fs.readJson(packagePath);
if (this.plugins.has(packageJson.name)) {
continue; // Skip already loaded plugin
}
}
await this.loadPlugin(pluginPath);
}
catch (error) {
const theme = await (0, neon_js_1.getTheme)();
console.log(theme.chalk.error(`${neon_js_1.neonSymbols.cross} Failed to load plugin ${entry}: ${error instanceof Error ? error.message : 'Unknown error'}`));
}
}
}
}
catch (error) {
// Plugin directory doesn't exist or other error - that's ok
}
}
// Load a single plugin with enhanced validation
async loadPlugin(pluginPath) {
const packagePath = path.join(pluginPath, 'package.json');
// Validate package.json exists and is readable
if (!await fs.pathExists(packagePath)) {
throw new Error('Plugin package.json not found');
}
let packageJson;
try {
packageJson = await fs.readJson(packagePath);
}
catch (error) {
throw new Error(`Invalid package.json: ${error instanceof Error ? error.message : 'Parse error'}`);
}
// Validate required package.json fields
if (!packageJson.name || typeof packageJson.name !== 'string') {
throw new Error('Plugin package.json must have a valid name field');
}
if (!packageJson.version || typeof packageJson.version !== 'string') {
throw new Error('Plugin package.json must have a valid version field');
}
// Check if plugin is already loaded (prevent conflicts)
if (this.plugins.has(packageJson.name)) {
throw new Error(`Plugin ${packageJson.name} is already loaded`);
}
// Find and validate main file
const mainFile = packageJson.main || 'index.js';
const mainPath = path.join(pluginPath, mainFile);
if (!await fs.pathExists(mainPath)) {
throw new Error(`Plugin main file not found: ${mainFile}`);
}
// Import the plugin with timeout protection
let pluginModule;
try {
const importPromise = Promise.resolve(`${mainPath}`).then(s => __importStar(require(s)));
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('Plugin import timeout (30s)')), 30000));
pluginModule = await Promise.race([importPromise, timeoutPromise]);
}
catch (error) {
throw new Error(`Failed to import plugin: ${error instanceof Error ? error.message : 'Import error'}`);
}
const plugin = pluginModule.default || pluginModule;
// Comprehensive plugin validation
if (!plugin || typeof plugin !== 'object') {
throw new Error('Plugin must export an object');
}
if (!plugin.name || typeof plugin.name !== 'string') {
throw new Error('Plugin must have a valid name property');
}
if (!plugin.description || typeof plugin.description !== 'string') {
throw new Error('Plugin must have a valid description property');
}
if (!plugin.version || typeof plugin.version !== 'string') {
throw new Error('Plugin must have a valid version property');
}
if (!plugin.activate || typeof plugin.activate !== 'function') {
throw new Error('Plugin must have an activate() function');
}
// Validate plugin name matches package.json
if (plugin.name !== packageJson.name) {
throw new Error(`Plugin name "${plugin.name}" doesn't match package.json name "${packageJson.name}"`);
}
// Create context for plugin
const context = this.createPluginContext(plugin.name);
// Activate plugin with timeout and error handling
try {
const activatePromise = plugin.activate(context);
if (activatePromise instanceof Promise) {
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('Plugin activation timeout (15s)')), 15000));
await Promise.race([activatePromise, timeoutPromise]);
}
}
catch (error) {
throw new Error(`Plugin activation failed: ${error instanceof Error ? error.message : 'Activation error'}`);
}
// Store loaded plugin
this.plugins.set(plugin.name, {
plugin,
context,
path: pluginPath,
packageJson
});
// Only show loading message in verbose mode to reduce noise
if (process.env.BEELINE_VERBOSE || process.env.DEBUG) {
const theme = await (0, neon_js_1.getTheme)();
console.log(theme.chalk.success(`${neon_js_1.neonSymbols.check} Loaded plugin: ${plugin.name} v${plugin.version}`));
}
}
// Create context for plugin
createPluginContext(pluginName) {
return {
addCommand: (name, description, handler) => {
this.registeredCommands.set(name, {
name,
description,
handler,
pluginName
});
},
addUICommand: (name, description, handler) => {
this.registeredCommands.set(name, {
name,
description,
handler: async (args, flags) => {
// UI commands need special context - this will be enhanced later
await handler(args, flags, null);
},
pluginName,
isUI: true,
uiHandler: handler
});
},
log: (message) => {
if (process.env.BEELINE_VERBOSE || process.env.DEBUG) {
console.log(`[${pluginName}] ${message}`);
}
},
success: (message) => {
console.log(`\x1b[32m[${pluginName}] ${message}\x1b[0m`);
},
error: (message) => {
console.log(`\x1b[31m[${pluginName}] ${message}\x1b[0m`);
},
ui: {
createForm: async (options) => {
// TODO: Implement form creation
throw new Error('Form creation not yet implemented');
},
showDialog: async (options) => {
// TODO: Implement dialog
throw new Error('Dialog not yet implemented');
},
showMenu: async (options) => {
// TODO: Implement menu
throw new Error('Menu not yet implemented');
},
blessed: (() => {
try {
return require('blessed');
}
catch (error) {
return null;
}
})()
},
wallet: {
getCurrentAccount: async () => {
try {
const { KeyManager } = require('./crypto.js');
const keyManager = new KeyManager();
await keyManager.initialize();
return keyManager.getDefaultAccount() || null;
}
catch {
return null;
}
},
getAccountList: async () => {
try {
const { KeyManager } = require('./crypto.js');
const keyManager = new KeyManager();
await keyManager.initialize();
return await keyManager.listAccounts();
}
catch {
return [];
}
},
getBalance: async (account) => {
const { HiveClient } = await Promise.resolve().then(() => __importStar(require('./hive.js')));
const { KeyManager } = await Promise.resolve().then(() => __importStar(require('./crypto.js')));
const keyManager = new KeyManager();
await keyManager.initialize();
const targetAccount = account || keyManager.getDefaultAccount();
if (!targetAccount) {
throw new Error('No account specified and no default account');
}
const hiveClient = new HiveClient(keyManager);
return hiveClient.getBalance(targetAccount);
},
broadcastCustomJson: async (account, id, json, requiredAuths = [], requiredPostingAuths = []) => {
const { HiveClient } = await Promise.resolve().then(() => __importStar(require('./hive.js')));
const { KeyManager } = await Promise.resolve().then(() => __importStar(require('./crypto.js')));
const keyManager = new KeyManager();
await keyManager.initialize();
const hiveClient = new HiveClient(keyManager);
// First try without PIN (for unencrypted keys)
try {
return await hiveClient.broadcastCustomJson(account, id, json, requiredAuths, requiredPostingAuths);
}
catch (error) {
// If it fails because PIN is required, prompt for PIN using inquirer (same as main commands)
if (error.message.includes('PIN required')) {
const { default: inquirer } = await Promise.resolve().then(() => __importStar(require('inquirer')));
const pinPrompt = await inquirer.prompt([{
type: 'password',
name: 'pin',
message: `🔐 Enter PIN to decrypt ${requiredAuths.length > 0 ? 'active' : 'posting'} key for @${account}:`,
validate: (input) => input.length > 0 || 'PIN required'
}]);
const pin = pinPrompt.pin;
// Clean up inquirer to prevent hanging
if (process.stdin && process.stdin.destroy) {
process.stdin.pause();
}
// Retry with PIN
try {
return await hiveClient.broadcastCustomJson(account, id, json, requiredAuths, requiredPostingAuths, pin);
}
catch (pinError) {
throw new Error(`Transaction failed: ${pinError.message}`);
}
}
throw error;
}
}
}
};
}
// Get list of installed plugins
getPlugins() {
return Array.from(this.plugins.values());
}
// Get registered commands
getCommands() {
return this.registeredCommands;
}
// Execute a plugin command
async executeCommand(commandName, args, flags) {
const command = this.registeredCommands.get(commandName);
if (!command) {
throw new Error(`Command not found: ${commandName}`);
}
try {
await command.handler(args, flags);
}
catch (error) {
const theme = await (0, neon_js_1.getTheme)();
console.log(theme.chalk.error(`${neon_js_1.neonSymbols.cross} Plugin command failed: ${error instanceof Error ? error.message : 'Unknown error'}`));
throw error;
}
}
// Uninstall a plugin
async uninstallPlugin(pluginName) {
const theme = await (0, neon_js_1.getTheme)();
const plugin = this.plugins.get(pluginName);
if (!plugin) {
throw new Error(`Plugin not found: ${pluginName}`);
}
try {
// Deactivate plugin
if (plugin.plugin.deactivate) {
await plugin.plugin.deactivate();
}
// Remove registered commands
for (const [cmdName, cmd] of this.registeredCommands.entries()) {
if (cmd.pluginName === pluginName) {
this.registeredCommands.delete(cmdName);
}
}
// Remove from memory
this.plugins.delete(pluginName);
// Remove files
await fs.remove(plugin.path);
console.log(theme.chalk.success(`${neon_js_1.neonSymbols.check} Plugin uninstalled: ${pluginName}`));
}
catch (error) {
console.log(theme.chalk.error(`${neon_js_1.neonSymbols.cross} Failed to uninstall plugin: ${error instanceof Error ? error.message : 'Unknown error'}`));
throw error;
}
}
}
exports.SimplePluginManager = SimplePluginManager;
// Global instance
let pluginManager = null;
function getPluginManager() {
if (!pluginManager) {
pluginManager = new SimplePluginManager();
}
return pluginManager;
}
//# sourceMappingURL=simple-plugins.js.map