caravan-x
Version:
A terminal-based utility for managing Caravan multisig wallets in regtest mode. This tool simplifies development and testing with Caravan by providing an easy-to-use interface
444 lines (443 loc) ⢠19.8 kB
JavaScript
"use strict";
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;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SettingsCommands = void 0;
const prompts_1 = require("@inquirer/prompts");
const chalk_1 = __importDefault(require("chalk"));
const boxen_1 = __importDefault(require("boxen"));
const fs = __importStar(require("fs-extra"));
const path = __importStar(require("path"));
const profiles_1 = require("../core/profiles");
const setupWizard_1 = require("../ui/setupWizard");
const config_1 = require("../types/config");
class SettingsCommands {
constructor(appDir) {
this.appDir = appDir;
this.configPath = path.join(appDir, "config.json");
this.profileManager = new profiles_1.ProfileManager(appDir);
}
async showSettingsMenu() {
let continueMenu = true;
while (continueMenu) {
console.clear();
console.log(chalk_1.default.cyan.bold("\nāļø Settings\n"));
console.log(chalk_1.default.gray("ā".repeat(50)) + "\n");
const action = await (0, prompts_1.select)({
message: "What would you like to do?",
choices: [
{ name: chalk_1.default.cyan("š View All Configurations"), value: "view_all" },
{ name: chalk_1.default.yellow("š Switch Mode"), value: "switch_mode" },
{ name: chalk_1.default.green("š Edit Current Config"), value: "edit" },
{ name: chalk_1.default.magenta("š Manage Profiles"), value: "profiles" },
{ name: chalk_1.default.gray("š Back"), value: "back" },
],
});
switch (action) {
case "view_all":
await this.viewAllConfigurations();
break;
case "switch_mode":
await this.switchMode();
break;
case "edit":
await this.editCurrentConfig();
break;
case "profiles":
await this.manageProfiles();
break;
case "back":
continueMenu = false;
break;
}
}
}
/**
* View all configurations (Docker + Manual)
*/
async viewAllConfigurations() {
console.clear();
console.log(chalk_1.default.cyan.bold("\nš All Configurations\n"));
// Get current config
let currentConfig = null;
try {
currentConfig = await fs.readJson(this.configPath);
}
catch (error) {
// No config
}
// Get all profiles
const allProfiles = await this.profileManager.listProfiles();
const dockerProfiles = allProfiles.filter((p) => p.mode === config_1.SetupMode.DOCKER);
const manualProfiles = allProfiles.filter((p) => p.mode === config_1.SetupMode.MANUAL);
// Docker configurations
console.log((0, boxen_1.default)(chalk_1.default.cyan.bold("š³ DOCKER CONFIGURATIONS\n\n") +
(dockerProfiles.length > 0
? await this.formatProfilesList(dockerProfiles)
: chalk_1.default.gray("No Docker configurations")), {
padding: 1,
margin: { top: 0, bottom: 1, left: 0, right: 0 },
borderStyle: "round",
borderColor: "cyan",
}));
// Manual configurations
console.log((0, boxen_1.default)(chalk_1.default.yellow.bold("āļø MANUAL CONFIGURATIONS\n\n") +
(manualProfiles.length > 0
? await this.formatProfilesList(manualProfiles)
: chalk_1.default.gray("No Manual configurations")), {
padding: 1,
margin: { top: 0, bottom: 1, left: 0, right: 0 },
borderStyle: "round",
borderColor: "yellow",
}));
// Current active config
if (currentConfig) {
console.log((0, boxen_1.default)(chalk_1.default.green.bold("ā
CURRENTLY ACTIVE\n\n") +
this.formatConfigDetails(currentConfig), {
padding: 1,
margin: { top: 0, bottom: 1, left: 0, right: 0 },
borderStyle: "double",
borderColor: "green",
}));
}
await this.pressEnter();
}
/**
* Format profiles list
*/
async formatProfilesList(profiles) {
const lines = [];
for (const profile of profiles) {
try {
const config = await fs.readJson(profile.configPath);
lines.push(chalk_1.default.white.bold(`š ${profile.name}`));
lines.push(chalk_1.default.dim(" Last used: ") +
chalk_1.default.white(new Date(profile.lastUsedAt).toLocaleString()));
lines.push(chalk_1.default.dim(" RPC: ") +
chalk_1.default.white(`${config.bitcoin.host}:${config.bitcoin.port}`));
if (config.docker) {
lines.push(chalk_1.default.dim(" Container: ") +
chalk_1.default.cyan(config.docker.containerName));
}
lines.push("");
}
catch (error) {
lines.push(chalk_1.default.red(`Error loading: ${profile.name}\n`));
}
}
return lines.join("\n");
}
/**
* Format config details
*/
formatConfigDetails(config) {
const lines = [];
const modeBadge = config.mode === config_1.SetupMode.DOCKER
? chalk_1.default.bgCyan.black(" š³ DOCKER ")
: chalk_1.default.bgYellow.black(" āļø MANUAL ");
lines.push(chalk_1.default.white("Mode: ") + modeBadge + "\n");
lines.push(chalk_1.default.white.bold("š” Bitcoin RPC:"));
lines.push(chalk_1.default.dim(" Protocol: ") + chalk_1.default.white(config.bitcoin.protocol));
lines.push(chalk_1.default.dim(" Host: ") + chalk_1.default.white(config.bitcoin.host));
lines.push(chalk_1.default.dim(" Port: ") + chalk_1.default.cyan(config.bitcoin.port.toString()));
lines.push(chalk_1.default.dim(" User: ") + chalk_1.default.white(config.bitcoin.user));
lines.push(chalk_1.default.dim(" Data Dir: ") + chalk_1.default.white(config.bitcoin.dataDir));
lines.push("");
lines.push(chalk_1.default.white.bold("š Directories:"));
lines.push(chalk_1.default.dim(" App: ") + chalk_1.default.white(config.appDir));
lines.push(chalk_1.default.dim(" Wallets: ") + chalk_1.default.white(config.caravanDir));
lines.push(chalk_1.default.dim(" Keys: ") + chalk_1.default.white(config.keysDir));
if (config.mode === config_1.SetupMode.DOCKER && config.docker) {
lines.push("");
lines.push(chalk_1.default.white.bold("š³ Docker:"));
lines.push(chalk_1.default.dim(" Container: ") + chalk_1.default.cyan(config.docker.containerName));
lines.push(chalk_1.default.dim(" Image: ") + chalk_1.default.white(config.docker.image));
lines.push(chalk_1.default.dim(" Network: ") + chalk_1.default.white(config.docker.network));
lines.push(chalk_1.default.dim(" RPC Port: ") +
chalk_1.default.white(config.docker.ports.rpc.toString()));
lines.push(chalk_1.default.dim(" P2P Port: ") +
chalk_1.default.white(config.docker.ports.p2p.toString()));
lines.push(chalk_1.default.dim(" Bitcoin Data: ") +
chalk_1.default.white(config.docker.volumes.bitcoinData));
}
return lines.join("\n");
}
/**
* Switch between modes
*/
async switchMode() {
console.clear();
console.log(chalk_1.default.cyan.bold("\nš Switch Mode\n"));
let currentConfig;
try {
currentConfig = await fs.readJson(this.configPath);
}
catch (error) {
console.log(chalk_1.default.red("No current configuration found"));
await this.pressEnter();
return;
}
const currentMode = currentConfig.mode;
const targetMode = currentMode === config_1.SetupMode.DOCKER ? config_1.SetupMode.MANUAL : config_1.SetupMode.DOCKER;
const currentBadge = currentMode === config_1.SetupMode.DOCKER
? chalk_1.default.bgCyan.black(" š³ Docker ")
: chalk_1.default.bgYellow.black(" āļø Manual ");
const targetBadge = targetMode === config_1.SetupMode.DOCKER
? chalk_1.default.bgCyan.black(" š³ Docker ")
: chalk_1.default.bgYellow.black(" āļø Manual ");
console.log(chalk_1.default.white("Current: ") + currentBadge);
console.log(chalk_1.default.white("Switch to: ") + targetBadge + "\n");
// Check for existing configs in target mode
const targetProfiles = await this.profileManager.getProfilesByMode(targetMode);
if (targetProfiles.length > 0) {
console.log(chalk_1.default.green(`Found ${targetProfiles.length} existing ${targetMode} config(s)\n`));
const choice = await (0, prompts_1.select)({
message: "Select action:",
choices: [
...targetProfiles.map((p) => ({
name: `š Use: ${p.name}`,
value: p.id,
})),
{ name: chalk_1.default.cyan("ā Create new configuration"), value: "new" },
{ name: chalk_1.default.gray("š Cancel"), value: "cancel" },
],
});
if (choice === "cancel")
return;
if (choice === "new") {
await this.createAndSwitchToNew(targetMode);
}
else {
// Load selected profile
const profile = await this.profileManager.getProfile(choice);
if (profile) {
await fs.writeJson(this.configPath, profile.config, { spaces: 2 });
await this.profileManager.setActiveProfile(profile.id);
console.log(chalk_1.default.green(`\nā
Switched to: ${profile.name}`));
console.log(chalk_1.default.yellow("\nRestart Caravan-X for changes to take effect."));
await this.pressEnter();
}
}
}
else {
console.log(chalk_1.default.yellow(`No ${targetMode} configurations found.\n`));
const create = await (0, prompts_1.confirm)({
message: `Create a new ${targetMode} configuration?`,
default: true,
});
if (create) {
await this.createAndSwitchToNew(targetMode);
}
}
}
/**
* Create new config and switch to it
*/
async createAndSwitchToNew(mode) {
const wizard = new setupWizard_1.SetupWizard(this.appDir);
let config;
if (mode === config_1.SetupMode.DOCKER) {
config = await wizard.setupDockerMode(true); // Skip docker start
}
else {
config = await wizard.setupManualMode();
}
const profileName = await (0, prompts_1.input)({
message: "Name this configuration:",
default: `${mode === config_1.SetupMode.DOCKER ? "Docker" : "Manual"} Config`,
});
const profile = await this.profileManager.createProfile(profileName, mode, config);
await fs.writeJson(this.configPath, config, { spaces: 2 });
await this.profileManager.setActiveProfile(profile.id);
console.log(chalk_1.default.green(`\nā
Created and switched to: ${profileName}`));
console.log(chalk_1.default.yellow("\nRestart Caravan-X for changes to take effect."));
await this.pressEnter();
}
/**
* Edit current configuration
*/
async editCurrentConfig() {
console.clear();
console.log(chalk_1.default.cyan.bold("\nš Edit Current Configuration\n"));
let config;
try {
config = await fs.readJson(this.configPath);
}
catch (error) {
console.log(chalk_1.default.red("No configuration found"));
await this.pressEnter();
return;
}
const choice = await (0, prompts_1.select)({
message: "What to edit?",
choices: [
{ name: "š” RPC Settings", value: "rpc" },
{ name: "š Directories", value: "dirs" },
...(config.mode === config_1.SetupMode.DOCKER
? [{ name: "š³ Docker Settings", value: "docker" }]
: []),
{ name: chalk_1.default.gray("š Back"), value: "back" },
],
});
if (choice === "back")
return;
if (choice === "rpc") {
config.bitcoin.host = await (0, prompts_1.input)({
message: "Host:",
default: config.bitcoin.host,
});
config.bitcoin.port = parseInt(await (0, prompts_1.input)({
message: "Port:",
default: config.bitcoin.port.toString(),
}));
config.bitcoin.user = await (0, prompts_1.input)({
message: "User:",
default: config.bitcoin.user,
});
config.bitcoin.pass = await (0, prompts_1.input)({
message: "Password:",
default: config.bitcoin.pass,
});
}
if (choice === "dirs") {
config.caravanDir = await (0, prompts_1.input)({
message: "Wallets directory:",
default: config.caravanDir,
});
config.keysDir = await (0, prompts_1.input)({
message: "Keys directory:",
default: config.keysDir,
});
await fs.ensureDir(config.caravanDir);
await fs.ensureDir(config.keysDir);
}
if (choice === "docker" && config.docker) {
config.docker.containerName = await (0, prompts_1.input)({
message: "Container name:",
default: config.docker.containerName,
});
config.docker.ports.rpc = parseInt(await (0, prompts_1.input)({
message: "RPC port:",
default: config.docker.ports.rpc.toString(),
}));
config.docker.ports.p2p = parseInt(await (0, prompts_1.input)({
message: "P2P port:",
default: config.docker.ports.p2p.toString(),
}));
}
await fs.writeJson(this.configPath, config, { spaces: 2 });
console.log(chalk_1.default.green("\nā
Configuration updated!"));
if (choice === "docker") {
console.log(chalk_1.default.yellow("Restart Docker containers for changes to take effect."));
}
await this.pressEnter();
}
/**
* Manage saved profiles
*/
async manageProfiles() {
console.clear();
console.log(chalk_1.default.cyan.bold("\nš Manage Profiles\n"));
const profiles = await this.profileManager.listProfiles();
if (profiles.length === 0) {
console.log(chalk_1.default.gray("No saved profiles."));
await this.pressEnter();
return;
}
const choice = await (0, prompts_1.select)({
message: "Select profile:",
choices: [
...profiles.map((p) => ({
name: `${p.mode === config_1.SetupMode.DOCKER ? "š³" : "āļø "} ${p.name}`,
value: p.id,
})),
{ name: chalk_1.default.gray("š Back"), value: "back" },
],
});
if (choice === "back")
return;
// Manage selected profile
const profile = await this.profileManager.getProfile(choice);
if (!profile)
return;
console.log((0, boxen_1.default)(chalk_1.default.white.bold(`š ${profile.name}\n\n`) +
this.formatConfigDetails(profile.config), {
padding: 1,
margin: 1,
borderStyle: "round",
borderColor: profile.mode === config_1.SetupMode.DOCKER ? "cyan" : "yellow",
}));
const action = await (0, prompts_1.select)({
message: "Action:",
choices: [
{ name: chalk_1.default.green("ā
Set as Active"), value: "activate" },
{ name: chalk_1.default.yellow("āļø Rename"), value: "rename" },
{ name: chalk_1.default.red("šļø Delete"), value: "delete" },
{ name: chalk_1.default.gray("š Back"), value: "back" },
],
});
if (action === "activate") {
await fs.writeJson(this.configPath, profile.config, { spaces: 2 });
await this.profileManager.setActiveProfile(profile.id);
console.log(chalk_1.default.green(`\nā
${profile.name} is now active!`));
console.log(chalk_1.default.yellow("Restart Caravan-X for changes to take effect."));
}
if (action === "rename") {
const newName = await (0, prompts_1.input)({
message: "New name:",
default: profile.name,
});
await this.profileManager.renameProfile(profile.id, newName);
console.log(chalk_1.default.green(`\nā
Renamed to: ${newName}`));
}
if (action === "delete") {
const confirmDel = await (0, prompts_1.confirm)({
message: `Delete "${profile.name}"?`,
default: false,
});
if (confirmDel) {
await this.profileManager.deleteProfile(profile.id);
console.log(chalk_1.default.green("\nā
Deleted"));
}
}
await this.pressEnter();
}
async pressEnter() {
await (0, prompts_1.input)({ message: chalk_1.default.gray("Press Enter to continue...") });
}
}
exports.SettingsCommands = SettingsCommands;