@johpaz/hive-cli 1.0.2 → 1.0.4
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/package.json +4 -2
- package/src/commands/agents.ts +187 -0
- package/src/commands/chat.ts +140 -0
- package/src/commands/config.ts +168 -0
- package/src/commands/cron.ts +157 -0
- package/src/commands/doctor.ts +153 -0
- package/src/commands/gateway.ts +132 -0
- package/src/commands/logs.ts +57 -0
- package/src/commands/mcp.ts +215 -0
- package/src/commands/onboard.ts +687 -0
- package/src/commands/security.ts +189 -0
- package/src/commands/service.ts +50 -0
- package/src/commands/sessions.ts +116 -0
- package/src/commands/skills.ts +187 -0
- package/src/commands/update.ts +25 -0
- package/src/index.ts +149 -73
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import * as p from "@clack/prompts";
|
|
2
|
+
import * as fs from "fs";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
import * as yaml from "js-yaml";
|
|
5
|
+
import { execSync } from "child_process";
|
|
6
|
+
|
|
7
|
+
const HIVE_DIR = path.join(process.env.HOME || "", ".hive");
|
|
8
|
+
const CONFIG_PATH = path.join(HIVE_DIR, "hive.yaml");
|
|
9
|
+
const WORKSPACE = path.join(HIVE_DIR, "workspace");
|
|
10
|
+
const PID_FILE = path.join(HIVE_DIR, "hive.pid");
|
|
11
|
+
|
|
12
|
+
function checkFile(path: string, name: string): { ok: boolean; message: string } {
|
|
13
|
+
if (fs.existsSync(path)) {
|
|
14
|
+
const stat = fs.statSync(path);
|
|
15
|
+
const mode = (stat.mode & 0o777).toString(8);
|
|
16
|
+
return { ok: true, message: `${name} existe (permisos: ${mode})` };
|
|
17
|
+
}
|
|
18
|
+
return { ok: false, message: `${name} no existe` };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function checkBun(): { ok: boolean; version: string } {
|
|
22
|
+
try {
|
|
23
|
+
const version = execSync("bun --version", { encoding: "utf-8" }).trim();
|
|
24
|
+
return { ok: true, version };
|
|
25
|
+
} catch {
|
|
26
|
+
return { ok: false, version: "no instalado" };
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function checkNode(): { ok: boolean; version: string } {
|
|
31
|
+
try {
|
|
32
|
+
const version = execSync("node --version", { encoding: "utf-8" }).trim();
|
|
33
|
+
return { ok: true, version };
|
|
34
|
+
} catch {
|
|
35
|
+
return { ok: false, version: "no instalado" };
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function isGatewayRunning(): boolean {
|
|
40
|
+
if (!fs.existsSync(PID_FILE)) return false;
|
|
41
|
+
const pid = parseInt(fs.readFileSync(PID_FILE, "utf-8").trim(), 10);
|
|
42
|
+
if (isNaN(pid)) return false;
|
|
43
|
+
try {
|
|
44
|
+
process.kill(pid, 0);
|
|
45
|
+
return true;
|
|
46
|
+
} catch {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export async function doctor(): Promise<void> {
|
|
52
|
+
console.log("\n🐝 Hive Doctor — Diagnóstico del sistema\n");
|
|
53
|
+
|
|
54
|
+
const checks: Array<{ category: string; name: string; status: "ok" | "warn" | "error"; message: string; hint?: string }> = [];
|
|
55
|
+
|
|
56
|
+
// Runtime
|
|
57
|
+
const bun = checkBun();
|
|
58
|
+
checks.push({ category: "Runtime", name: "Bun", status: bun.ok ? "ok" : "error", message: `v${bun.version}` });
|
|
59
|
+
|
|
60
|
+
const node = checkNode();
|
|
61
|
+
checks.push({ category: "Runtime", name: "Node.js", status: node.ok ? "ok" : "warn", message: `${node.version} (para MCP servers)` });
|
|
62
|
+
|
|
63
|
+
// Configuración
|
|
64
|
+
const configCheck = checkFile(CONFIG_PATH, "hive.yaml");
|
|
65
|
+
if (configCheck.ok) {
|
|
66
|
+
checks.push({ category: "Configuración", name: "hive.yaml", status: "ok", message: "existe" });
|
|
67
|
+
|
|
68
|
+
// Verificar permisos
|
|
69
|
+
const stat = fs.statSync(CONFIG_PATH);
|
|
70
|
+
const mode = stat.mode & 0o777;
|
|
71
|
+
if (mode === 0o600) {
|
|
72
|
+
checks.push({ category: "Configuración", name: "Permisos", status: "ok", message: "600 (seguro)" });
|
|
73
|
+
} else {
|
|
74
|
+
checks.push({ category: "Configuración", name: "Permisos", status: "warn", message: `${(mode).toString(8)} — ejecuta: chmod 600 ${CONFIG_PATH}` });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Verificar LLM config
|
|
78
|
+
try {
|
|
79
|
+
const content = fs.readFileSync(CONFIG_PATH, "utf-8");
|
|
80
|
+
const config = yaml.load(content) as Record<string, unknown>;
|
|
81
|
+
const models = config.models as Record<string, unknown> | undefined;
|
|
82
|
+
const provider = models?.defaultProvider as string | undefined;
|
|
83
|
+
|
|
84
|
+
if (provider) {
|
|
85
|
+
checks.push({ category: "Configuración", name: "Proveedor LLM", status: "ok", message: provider });
|
|
86
|
+
|
|
87
|
+
const providers = models?.providers as Record<string, Record<string, unknown>> | undefined;
|
|
88
|
+
const providerConfig = providers?.[provider] as Record<string, unknown> | undefined;
|
|
89
|
+
const apiKey = providerConfig?.apiKey as string | undefined;
|
|
90
|
+
|
|
91
|
+
if (apiKey && apiKey.length > 0) {
|
|
92
|
+
checks.push({ category: "Configuración", name: "API Key", status: "ok", message: "presente" });
|
|
93
|
+
} else {
|
|
94
|
+
checks.push({ category: "Configuración", name: "API Key", status: "warn", message: "no configurada" });
|
|
95
|
+
}
|
|
96
|
+
} else {
|
|
97
|
+
checks.push({ category: "Configuración", name: "Proveedor LLM", status: "error", message: "no configurado" });
|
|
98
|
+
}
|
|
99
|
+
} catch (e) {
|
|
100
|
+
checks.push({ category: "Configuración", name: "hive.yaml", status: "error", message: `error parseando: ${(e as Error).message}` });
|
|
101
|
+
}
|
|
102
|
+
} else {
|
|
103
|
+
checks.push({ category: "Configuración", name: "hive.yaml", status: "error", message: "no existe — ejecuta: hive onboard" });
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Workspace
|
|
107
|
+
checks.push({ category: "Workspace", name: "Directorio", status: fs.existsSync(WORKSPACE) ? "ok" : "warn", message: fs.existsSync(WORKSPACE) ? "existe" : "no existe" });
|
|
108
|
+
checks.push({ category: "Workspace", name: "SOUL.md", status: fs.existsSync(path.join(WORKSPACE, "SOUL.md")) ? "ok" : "warn", message: fs.existsSync(path.join(WORKSPACE, "SOUL.md")) ? "presente" : "falta" });
|
|
109
|
+
checks.push({ category: "Workspace", name: "USER.md", status: fs.existsSync(path.join(WORKSPACE, "USER.md")) ? "ok" : "warn", message: fs.existsSync(path.join(WORKSPACE, "USER.md")) ? "presente" : "falta" });
|
|
110
|
+
checks.push({ category: "Workspace", name: "ETHICS.md", status: fs.existsSync(path.join(WORKSPACE, "ETHICS.md")) ? "ok" : "warn", message: fs.existsSync(path.join(WORKSPACE, "ETHICS.md")) ? "presente" : "falta" });
|
|
111
|
+
|
|
112
|
+
// Gateway
|
|
113
|
+
const running = isGatewayRunning();
|
|
114
|
+
checks.push({ category: "Gateway", name: "Estado", status: running ? "ok" : "warn", message: running ? "corriendo" : "detenido" });
|
|
115
|
+
|
|
116
|
+
// Mostrar resultados
|
|
117
|
+
const categories = [...new Set(checks.map((c) => c.category))];
|
|
118
|
+
|
|
119
|
+
for (const category of categories) {
|
|
120
|
+
console.log(`${category}`);
|
|
121
|
+
const categoryChecks = checks.filter((c) => c.category === category);
|
|
122
|
+
for (const check of categoryChecks) {
|
|
123
|
+
const icon = check.status === "ok" ? "✅" : check.status === "warn" ? "⚠️ " : "❌";
|
|
124
|
+
console.log(` ${icon} ${check.name}: ${check.message}`);
|
|
125
|
+
if (check.hint) {
|
|
126
|
+
console.log(` 💡 ${check.hint}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
console.log();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Resumen
|
|
133
|
+
const errors = checks.filter((c) => c.status === "error");
|
|
134
|
+
const warns = checks.filter((c) => c.status === "warn");
|
|
135
|
+
|
|
136
|
+
if (errors.length > 0) {
|
|
137
|
+
console.log(`❌ ${errors.length} error(es) encontrado(s)`);
|
|
138
|
+
|
|
139
|
+
const fix = await p.confirm({
|
|
140
|
+
message: "¿Deseas ejecutar el onboarding para reparar?",
|
|
141
|
+
initialValue: false,
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
if (fix) {
|
|
145
|
+
const { onboard } = await import("./onboard");
|
|
146
|
+
await onboard();
|
|
147
|
+
}
|
|
148
|
+
} else if (warns.length > 0) {
|
|
149
|
+
console.log(`⚠️ ${warns.length} advertencia(s)`);
|
|
150
|
+
} else {
|
|
151
|
+
console.log("✅ Todo en orden");
|
|
152
|
+
}
|
|
153
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { loadConfig, startGateway, logger } from "@johpaz/hive-core";
|
|
2
|
+
import * as p from "@clack/prompts";
|
|
3
|
+
import * as fs from "fs";
|
|
4
|
+
import * as path from "path";
|
|
5
|
+
import { spawn, spawnSync, ChildProcess } from "child_process";
|
|
6
|
+
|
|
7
|
+
const PID_FILE = path.join(process.env.HOME || "", ".hive", "hive.pid");
|
|
8
|
+
const LOG_FILE = path.join(process.env.HOME || "", ".hive", "logs", "gateway.log");
|
|
9
|
+
|
|
10
|
+
function ensureLogDir(): void {
|
|
11
|
+
const logDir = path.dirname(LOG_FILE);
|
|
12
|
+
if (!fs.existsSync(logDir)) {
|
|
13
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function isRunning(): boolean {
|
|
18
|
+
if (!fs.existsSync(PID_FILE)) return false;
|
|
19
|
+
const pid = parseInt(fs.readFileSync(PID_FILE, "utf-8").trim(), 10);
|
|
20
|
+
if (isNaN(pid)) return false;
|
|
21
|
+
try {
|
|
22
|
+
process.kill(pid, 0);
|
|
23
|
+
return true;
|
|
24
|
+
} catch {
|
|
25
|
+
fs.unlinkSync(PID_FILE);
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function start(flags: string[]): Promise<void> {
|
|
31
|
+
const daemon = flags.includes("--daemon");
|
|
32
|
+
|
|
33
|
+
if (isRunning()) {
|
|
34
|
+
console.log("⚠️ Hive Gateway ya está corriendo");
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const config = loadConfig();
|
|
39
|
+
|
|
40
|
+
if (config.logging?.level) {
|
|
41
|
+
logger.setLevel(config.logging.level);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
console.log(`
|
|
45
|
+
╔═══════════════════════════════════════════╗
|
|
46
|
+
║ ║
|
|
47
|
+
║ ██╗ ██╗██╗██╗ ██╗███████╗ ║
|
|
48
|
+
║ ██║ ██║██║██║ ██║██╔════╝ ║
|
|
49
|
+
║ ███████║██║██║ ██║█████╗ ║
|
|
50
|
+
║ ██╔══██║██║╚██╗ ██╔╝██╔══╝ ║
|
|
51
|
+
║ ██║ ██║██║ ╚████╔╝ ███████╗ ║
|
|
52
|
+
║ ╚═╝ ╚═╝╚═╝ ╚═══╝ ╚══════╝ ║
|
|
53
|
+
║ ║
|
|
54
|
+
║ Personal AI Gateway — v1.0.4 ║
|
|
55
|
+
╚════════════════════════════════════════════╝
|
|
56
|
+
`);
|
|
57
|
+
|
|
58
|
+
if (daemon) {
|
|
59
|
+
ensureLogDir();
|
|
60
|
+
const child = spawn(process.execPath, [process.argv[1] || "", "start"], {
|
|
61
|
+
detached: true,
|
|
62
|
+
stdio: ["ignore", fs.openSync(LOG_FILE, "a"), fs.openSync(LOG_FILE, "a")],
|
|
63
|
+
});
|
|
64
|
+
child.unref();
|
|
65
|
+
fs.writeFileSync(PID_FILE, child.pid?.toString() || "");
|
|
66
|
+
console.log(`✅ Hive Gateway iniciado en modo daemon (PID: ${child.pid})`);
|
|
67
|
+
console.log(` Logs: ${LOG_FILE}`);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
await startGateway(config);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export async function stop(): Promise<void> {
|
|
75
|
+
if (!isRunning()) {
|
|
76
|
+
console.log("⚠️ Hive Gateway no está corriendo");
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const pid = parseInt(fs.readFileSync(PID_FILE, "utf-8").trim(), 10);
|
|
81
|
+
try {
|
|
82
|
+
process.kill(pid, "SIGTERM");
|
|
83
|
+
fs.unlinkSync(PID_FILE);
|
|
84
|
+
console.log("✅ Hive Gateway detenido");
|
|
85
|
+
} catch (e) {
|
|
86
|
+
console.error("❌ Error deteniendo el Gateway:", e);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export async function status(flags: string[]): Promise<void> {
|
|
91
|
+
const running = isRunning();
|
|
92
|
+
const configPath = path.join(process.env.HOME || "", ".hive", "hive.yaml");
|
|
93
|
+
|
|
94
|
+
console.log("🐝 Hive Gateway Status\n");
|
|
95
|
+
|
|
96
|
+
if (!fs.existsSync(configPath)) {
|
|
97
|
+
console.log("❌ No hay configuración. Ejecuta: hive onboard");
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const config = loadConfig();
|
|
102
|
+
|
|
103
|
+
console.log(`Estado: ${running ? "✅ Corriendo" : "⏹️ Detenido"}`);
|
|
104
|
+
if (running) {
|
|
105
|
+
const pid = fs.readFileSync(PID_FILE, "utf-8").trim();
|
|
106
|
+
console.log(`PID: ${pid}`);
|
|
107
|
+
}
|
|
108
|
+
console.log(`Puerto: ${config.gateway?.port || 18790}`);
|
|
109
|
+
console.log(`Host: ${config.gateway?.host || "127.0.0.1"}`);
|
|
110
|
+
console.log(`Modelo: ${config.models?.defaultProvider || "no configurado"} / ${(config.models?.defaults as Record<string, string>)?.default || "no configurado"}`);
|
|
111
|
+
console.log(`Config: ${configPath}`);
|
|
112
|
+
console.log(`Logs: ${LOG_FILE}`);
|
|
113
|
+
|
|
114
|
+
if (flags.includes("--json")) {
|
|
115
|
+
console.log("\n" + JSON.stringify({ running, pid: running ? fs.readFileSync(PID_FILE, "utf-8").trim() : null, config }, null, 2));
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export async function reload(): Promise<void> {
|
|
120
|
+
if (!isRunning()) {
|
|
121
|
+
console.log("⚠️ Hive Gateway no está corriendo");
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const pid = parseInt(fs.readFileSync(PID_FILE, "utf-8").trim(), 10);
|
|
126
|
+
try {
|
|
127
|
+
process.kill(pid, "SIGHUP");
|
|
128
|
+
console.log("✅ Configuración recargada");
|
|
129
|
+
} catch (e) {
|
|
130
|
+
console.error("❌ Error recargando configuración:", e);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import { spawn } from "child_process";
|
|
4
|
+
|
|
5
|
+
const LOG_DIR = path.join(process.env.HOME || "", ".hive", "logs");
|
|
6
|
+
const LOG_FILE = path.join(LOG_DIR, "gateway.log");
|
|
7
|
+
|
|
8
|
+
export async function logs(flags: string[]): Promise<void> {
|
|
9
|
+
if (flags.includes("--clear")) {
|
|
10
|
+
await clearLogs();
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (!fs.existsSync(LOG_FILE)) {
|
|
15
|
+
console.log("No hay logs disponibles");
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const follow = flags.includes("--follow") || flags.includes("-f");
|
|
20
|
+
const levelFlag = flags.find((f) => f.startsWith("--level"));
|
|
21
|
+
const level = levelFlag ? levelFlag.split("=")[1] || flags[flags.indexOf(levelFlag) + 1] : null;
|
|
22
|
+
const agentFlag = flags.find((f) => f.startsWith("--agent"));
|
|
23
|
+
const agent = agentFlag ? agentFlag.split("=")[1] || flags[flags.indexOf(agentFlag) + 1] : null;
|
|
24
|
+
const lines = 100;
|
|
25
|
+
|
|
26
|
+
if (follow) {
|
|
27
|
+
const tail = spawn("tail", ["-f", LOG_FILE], { stdio: "inherit" });
|
|
28
|
+
process.on("SIGINT", () => {
|
|
29
|
+
tail.kill();
|
|
30
|
+
process.exit(0);
|
|
31
|
+
});
|
|
32
|
+
} else {
|
|
33
|
+
let content = fs.readFileSync(LOG_FILE, "utf-8");
|
|
34
|
+
const allLines = content.split("\n");
|
|
35
|
+
|
|
36
|
+
let filtered = allLines;
|
|
37
|
+
if (level) {
|
|
38
|
+
const levelLower = level.toLowerCase();
|
|
39
|
+
filtered = filtered.filter((l) => l.toLowerCase().includes(levelLower));
|
|
40
|
+
}
|
|
41
|
+
if (agent) {
|
|
42
|
+
filtered = filtered.filter((l) => l.includes(agent));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const lastLines = filtered.slice(-lines);
|
|
46
|
+
console.log(lastLines.join("\n"));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function clearLogs(): Promise<void> {
|
|
51
|
+
if (fs.existsSync(LOG_FILE)) {
|
|
52
|
+
fs.writeFileSync(LOG_FILE, "");
|
|
53
|
+
console.log("✅ Logs limpiados");
|
|
54
|
+
} else {
|
|
55
|
+
console.log("No hay logs para limpiar");
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import * as p from "@clack/prompts";
|
|
2
|
+
import * as fs from "fs";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
import * as yaml from "js-yaml";
|
|
5
|
+
import { spawn } from "child_process";
|
|
6
|
+
|
|
7
|
+
const CONFIG_PATH = path.join(process.env.HOME || "", ".hive", "hive.yaml");
|
|
8
|
+
|
|
9
|
+
interface MCPServerConfig {
|
|
10
|
+
command: string;
|
|
11
|
+
args?: string[];
|
|
12
|
+
env?: Record<string, string>;
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function loadConfig(): Record<string, unknown> | null {
|
|
17
|
+
if (!fs.existsSync(CONFIG_PATH)) return null;
|
|
18
|
+
const content = fs.readFileSync(CONFIG_PATH, "utf-8");
|
|
19
|
+
return yaml.load(content) as Record<string, unknown>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function saveConfig(config: Record<string, unknown>): void {
|
|
23
|
+
fs.writeFileSync(CONFIG_PATH, yaml.dump(config, { lineWidth: -1 }), "utf-8");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function mcp(subcommand: string | undefined, args: string[]): Promise<void> {
|
|
27
|
+
switch (subcommand) {
|
|
28
|
+
case "list":
|
|
29
|
+
await listMCP();
|
|
30
|
+
break;
|
|
31
|
+
case "add":
|
|
32
|
+
await addMCP();
|
|
33
|
+
break;
|
|
34
|
+
case "test":
|
|
35
|
+
await testMCP(args[0]);
|
|
36
|
+
break;
|
|
37
|
+
case "tools":
|
|
38
|
+
await toolsMCP(args[0]);
|
|
39
|
+
break;
|
|
40
|
+
case "remove":
|
|
41
|
+
await removeMCP(args[0]);
|
|
42
|
+
break;
|
|
43
|
+
default:
|
|
44
|
+
console.log(`
|
|
45
|
+
Usage: hive mcp <command>
|
|
46
|
+
|
|
47
|
+
Commands:
|
|
48
|
+
list Listar servidores MCP
|
|
49
|
+
add Añadir servidor MCP
|
|
50
|
+
test <nombre> Verificar servidor MCP
|
|
51
|
+
tools <nombre> Listar tools de un servidor
|
|
52
|
+
remove <nombre> Eliminar servidor MCP
|
|
53
|
+
`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function listMCP(): Promise<void> {
|
|
58
|
+
const config = loadConfig();
|
|
59
|
+
if (!config) {
|
|
60
|
+
console.log("❌ No hay configuración. Ejecuta: hive onboard");
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const mcpServers = (config.mcp as Record<string, MCPServerConfig>) || {};
|
|
65
|
+
|
|
66
|
+
if (Object.keys(mcpServers).length === 0) {
|
|
67
|
+
console.log("No hay servidores MCP configurados");
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
console.log("\n🔌 Servidores MCP:\n");
|
|
72
|
+
for (const [name, server] of Object.entries(mcpServers)) {
|
|
73
|
+
const status = server.disabled ? "⏸️ Deshabilitado" : "✅ Activo";
|
|
74
|
+
console.log(` ${name}`);
|
|
75
|
+
console.log(` Estado: ${status}`);
|
|
76
|
+
console.log(` Comando: ${server.command} ${(server.args || []).join(" ")}`);
|
|
77
|
+
console.log();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function addMCP(): Promise<void> {
|
|
82
|
+
const config = loadConfig();
|
|
83
|
+
if (!config) {
|
|
84
|
+
console.log("❌ No hay configuración. Ejecuta: hive onboard");
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const name = await p.text({
|
|
89
|
+
message: "Nombre del servidor MCP:",
|
|
90
|
+
placeholder: "filesystem",
|
|
91
|
+
validate: (v) => (!v ? "El nombre es requerido" : undefined),
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
if (p.isCancel(name)) {
|
|
95
|
+
p.cancel("Cancelado");
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const command = await p.text({
|
|
100
|
+
message: "Comando para ejecutar:",
|
|
101
|
+
placeholder: "npx",
|
|
102
|
+
validate: (v) => (!v ? "El comando es requerido" : undefined),
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
if (p.isCancel(command)) {
|
|
106
|
+
p.cancel("Cancelado");
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const argsInput = await p.text({
|
|
111
|
+
message: "Argumentos (separados por espacio):",
|
|
112
|
+
placeholder: "-y @modelcontextprotocol/server-filesystem /path/to/dir",
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
if (p.isCancel(argsInput)) {
|
|
116
|
+
p.cancel("Cancelado");
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const args = argsInput ? (argsInput as string).split(" ").filter(Boolean) : [];
|
|
121
|
+
|
|
122
|
+
const serverConfig: MCPServerConfig = {
|
|
123
|
+
command: command as string,
|
|
124
|
+
args,
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
if (!config.mcp) config.mcp = {};
|
|
128
|
+
(config.mcp as Record<string, MCPServerConfig>)[name as string] = serverConfig;
|
|
129
|
+
saveConfig(config);
|
|
130
|
+
|
|
131
|
+
console.log(`✅ Servidor MCP "${name}" añadido`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function testMCP(name: string | undefined): Promise<void> {
|
|
135
|
+
if (!name) {
|
|
136
|
+
console.log("❌ Especifica el nombre del servidor: hive mcp test <nombre>");
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const config = loadConfig();
|
|
141
|
+
if (!config) {
|
|
142
|
+
console.log("❌ No hay configuración.");
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const mcpServers = (config.mcp as Record<string, MCPServerConfig>) || {};
|
|
147
|
+
const server = mcpServers[name];
|
|
148
|
+
|
|
149
|
+
if (!server) {
|
|
150
|
+
console.log(`❌ No existe el servidor MCP "${name}"`);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
console.log(`🔌 Probando conexión con ${name}...`);
|
|
155
|
+
|
|
156
|
+
const proc = spawn(server.command, server.args || [], {
|
|
157
|
+
env: { ...process.env, ...server.env },
|
|
158
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
proc.on("error", (err) => {
|
|
162
|
+
console.log(`❌ Error: ${err.message}`);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
setTimeout(() => {
|
|
166
|
+
proc.kill();
|
|
167
|
+
console.log(`✅ Servidor ${name} responde`);
|
|
168
|
+
}, 2000);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async function toolsMCP(name: string | undefined): Promise<void> {
|
|
172
|
+
if (!name) {
|
|
173
|
+
console.log("❌ Especifica el nombre del servidor: hive mcp tools <nombre>");
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
console.log(`📋 Tools de ${name}:`);
|
|
178
|
+
console.log(" (Esta funcionalidad requiere conexión MCP activa)");
|
|
179
|
+
console.log(" Usa 'hive mcp test <nombre>' para verificar conexión");
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async function removeMCP(name: string | undefined): Promise<void> {
|
|
183
|
+
if (!name) {
|
|
184
|
+
console.log("❌ Especifica el nombre del servidor: hive mcp remove <nombre>");
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const config = loadConfig();
|
|
189
|
+
if (!config) {
|
|
190
|
+
console.log("❌ No hay configuración.");
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const mcpServers = (config.mcp as Record<string, MCPServerConfig>) || {};
|
|
195
|
+
|
|
196
|
+
if (!mcpServers[name]) {
|
|
197
|
+
console.log(`❌ No existe el servidor MCP "${name}"`);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const confirm = await p.confirm({
|
|
202
|
+
message: `¿Eliminar el servidor MCP "${name}"?`,
|
|
203
|
+
initialValue: false,
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
if (p.isCancel(confirm) || !confirm) {
|
|
207
|
+
console.log("Cancelado");
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
delete mcpServers[name];
|
|
212
|
+
saveConfig(config);
|
|
213
|
+
|
|
214
|
+
console.log(`✅ Servidor MCP "${name}" eliminado`);
|
|
215
|
+
}
|