@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,189 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import * as yaml from "js-yaml";
|
|
4
|
+
import { execSync } from "child_process";
|
|
5
|
+
|
|
6
|
+
const HIVE_DIR = path.join(process.env.HOME || "", ".hive");
|
|
7
|
+
const CONFIG_PATH = path.join(HIVE_DIR, "hive.yaml");
|
|
8
|
+
const WORKSPACE = path.join(HIVE_DIR, "workspace");
|
|
9
|
+
|
|
10
|
+
interface CheckResult {
|
|
11
|
+
category: string;
|
|
12
|
+
name: string;
|
|
13
|
+
status: "ok" | "warn" | "error";
|
|
14
|
+
message: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function check(name: string, pass: boolean, okMsg: string, failMsg: string): CheckResult {
|
|
18
|
+
return {
|
|
19
|
+
category: "",
|
|
20
|
+
name,
|
|
21
|
+
status: pass ? "ok" : "error",
|
|
22
|
+
message: pass ? okMsg : failMsg,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function securityAudit(): Promise<void> {
|
|
27
|
+
console.log("\n🔒 Hive Security Audit\n");
|
|
28
|
+
|
|
29
|
+
const results: CheckResult[] = [];
|
|
30
|
+
|
|
31
|
+
// Red
|
|
32
|
+
results.push({ category: "Red", name: "Gateway bind", status: "ok", message: "127.0.0.1 (loopback)" });
|
|
33
|
+
results.push({ category: "Red", name: "Puerto", status: "ok", message: "18790 (no expuesto externamente)" });
|
|
34
|
+
|
|
35
|
+
if (fs.existsSync(CONFIG_PATH)) {
|
|
36
|
+
try {
|
|
37
|
+
const content = fs.readFileSync(CONFIG_PATH, "utf-8");
|
|
38
|
+
const config = yaml.load(content) as Record<string, unknown>;
|
|
39
|
+
const gateway = config.gateway as Record<string, unknown> | undefined;
|
|
40
|
+
const token = gateway?.authToken as string | undefined;
|
|
41
|
+
|
|
42
|
+
results.push({
|
|
43
|
+
category: "Red",
|
|
44
|
+
name: "Token bearer",
|
|
45
|
+
status: token && token.length > 0 ? "ok" : "warn",
|
|
46
|
+
message: token && token.length > 0 ? "configurado" : "no configurado",
|
|
47
|
+
});
|
|
48
|
+
} catch {
|
|
49
|
+
results.push({ category: "Red", name: "Token bearer", status: "error", message: "error leyendo config" });
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Archivos
|
|
54
|
+
if (fs.existsSync(CONFIG_PATH)) {
|
|
55
|
+
const stat = fs.statSync(CONFIG_PATH);
|
|
56
|
+
const mode = stat.mode & 0o777;
|
|
57
|
+
results.push({
|
|
58
|
+
category: "Archivos",
|
|
59
|
+
name: "hive.yaml permisos",
|
|
60
|
+
status: mode === 0o600 ? "ok" : "warn",
|
|
61
|
+
message: mode === 0o600 ? "600 (seguro)" : `${(mode).toString(8)} — ejecuta: chmod 600 ${CONFIG_PATH}`,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const authProfiles = path.join(HIVE_DIR, "auth-profiles.json");
|
|
66
|
+
if (fs.existsSync(authProfiles)) {
|
|
67
|
+
const stat = fs.statSync(authProfiles);
|
|
68
|
+
const mode = stat.mode & 0o777;
|
|
69
|
+
results.push({
|
|
70
|
+
category: "Archivos",
|
|
71
|
+
name: "auth-profiles.json permisos",
|
|
72
|
+
status: mode === 0o600 ? "ok" : "warn",
|
|
73
|
+
message: mode === 0o600 ? "600 (seguro)" : `${(mode).toString(8)} — ejecuta: chmod 600 ${authProfiles}`,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const envFile = path.join(HIVE_DIR, ".env");
|
|
78
|
+
if (fs.existsSync(envFile)) {
|
|
79
|
+
const stat = fs.statSync(envFile);
|
|
80
|
+
const mode = stat.mode & 0o777;
|
|
81
|
+
results.push({
|
|
82
|
+
category: "Archivos",
|
|
83
|
+
name: ".env permisos",
|
|
84
|
+
status: mode === 0o600 ? "ok" : "warn",
|
|
85
|
+
message: mode === 0o600 ? "600 (seguro)" : `${(mode).toString(8)} — ejecuta: chmod 600 ${envFile}`,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Configuración
|
|
90
|
+
if (fs.existsSync(CONFIG_PATH)) {
|
|
91
|
+
try {
|
|
92
|
+
const content = fs.readFileSync(CONFIG_PATH, "utf-8");
|
|
93
|
+
const config = yaml.load(content) as Record<string, unknown>;
|
|
94
|
+
const models = config.models as Record<string, unknown> | undefined;
|
|
95
|
+
const providers = models?.providers as Record<string, Record<string, unknown>> | undefined;
|
|
96
|
+
|
|
97
|
+
let hasHardcodedKey = false;
|
|
98
|
+
if (providers) {
|
|
99
|
+
for (const [, p] of Object.entries(providers)) {
|
|
100
|
+
const apiKey = p.apiKey as string | undefined;
|
|
101
|
+
if (apiKey && !apiKey.startsWith("${") && !apiKey.startsWith("process.env")) {
|
|
102
|
+
hasHardcodedKey = true;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
results.push({
|
|
108
|
+
category: "Configuración",
|
|
109
|
+
name: "API keys",
|
|
110
|
+
status: !hasHardcodedKey ? "ok" : "warn",
|
|
111
|
+
message: !hasHardcodedKey ? "en variables de entorno" : "hardcodeadas en config — considera usar env vars",
|
|
112
|
+
});
|
|
113
|
+
} catch {
|
|
114
|
+
results.push({ category: "Configuración", name: "API keys", status: "warn", message: "no se pudo verificar" });
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Workspace
|
|
119
|
+
results.push({
|
|
120
|
+
category: "Workspace",
|
|
121
|
+
name: "Permisos workspace",
|
|
122
|
+
status: fs.existsSync(WORKSPACE) ? "ok" : "warn",
|
|
123
|
+
message: fs.existsSync(WORKSPACE) ? "existe" : "no existe",
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// MCP
|
|
127
|
+
if (fs.existsSync(CONFIG_PATH)) {
|
|
128
|
+
try {
|
|
129
|
+
const content = fs.readFileSync(CONFIG_PATH, "utf-8");
|
|
130
|
+
const config = yaml.load(content) as Record<string, unknown>;
|
|
131
|
+
const mcp = config.mcp as Record<string, unknown> | undefined;
|
|
132
|
+
const servers = mcp?.servers as Record<string, Record<string, unknown>> | undefined;
|
|
133
|
+
|
|
134
|
+
if (servers && Object.keys(servers).length > 0) {
|
|
135
|
+
results.push({ category: "MCP", name: "Servidores", status: "ok", message: `${Object.keys(servers).length} configurado(s)` });
|
|
136
|
+
|
|
137
|
+
for (const [name, server] of Object.entries(servers)) {
|
|
138
|
+
const cmd = server.command as string | undefined;
|
|
139
|
+
const isKnown = cmd?.includes("@modelcontextprotocol") || cmd?.includes("mcp-server");
|
|
140
|
+
results.push({
|
|
141
|
+
category: "MCP",
|
|
142
|
+
name: `Servidor '${name}'`,
|
|
143
|
+
status: isKnown ? "ok" : "warn",
|
|
144
|
+
message: isKnown ? "fuente conocida" : "verificar comando manualmente",
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
} else {
|
|
148
|
+
results.push({ category: "MCP", name: "Servidores", status: "ok", message: "ninguno configurado" });
|
|
149
|
+
}
|
|
150
|
+
} catch {
|
|
151
|
+
results.push({ category: "MCP", name: "Servidores", status: "warn", message: "error verificando" });
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Skills
|
|
156
|
+
const skillsDir = path.join(HIVE_DIR, "skills");
|
|
157
|
+
if (fs.existsSync(skillsDir)) {
|
|
158
|
+
const managedSkills = fs.readdirSync(skillsDir).filter((f) => {
|
|
159
|
+
return fs.statSync(path.join(skillsDir, f)).isDirectory();
|
|
160
|
+
});
|
|
161
|
+
results.push({
|
|
162
|
+
category: "Skills",
|
|
163
|
+
name: "Skills de terceros",
|
|
164
|
+
status: managedSkills.length === 0 ? "ok" : "warn",
|
|
165
|
+
message: managedSkills.length === 0 ? "ninguna instalada" : `${managedSkills.length} instalada(s) — verificar manualmente`,
|
|
166
|
+
});
|
|
167
|
+
} else {
|
|
168
|
+
results.push({ category: "Skills", name: "Skills de terceros", status: "ok", message: "ninguna instalada" });
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Mostrar resultados
|
|
172
|
+
const categories = [...new Set(results.map((r) => r.category))];
|
|
173
|
+
|
|
174
|
+
for (const category of categories) {
|
|
175
|
+
console.log(`${category}`);
|
|
176
|
+
const catResults = results.filter((r) => r.category === category);
|
|
177
|
+
for (const result of catResults) {
|
|
178
|
+
const icon = result.status === "ok" ? "✅" : result.status === "warn" ? "⚠️ " : "❌";
|
|
179
|
+
console.log(` ${icon} ${result.name}: ${result.message}`);
|
|
180
|
+
}
|
|
181
|
+
console.log();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Resumen
|
|
185
|
+
const errors = results.filter((r) => r.status === "error");
|
|
186
|
+
const warns = results.filter((r) => r.status === "warn");
|
|
187
|
+
|
|
188
|
+
console.log(`📊 Resumen: ${results.length} checks, ${errors.length} errores, ${warns.length} advertencias`);
|
|
189
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import { execSync } from "child_process";
|
|
4
|
+
|
|
5
|
+
const HOME = process.env.HOME || "";
|
|
6
|
+
const SYSTEMD_DIR = path.join(HOME, ".config", "systemd", "user");
|
|
7
|
+
|
|
8
|
+
export async function installService(): Promise<void> {
|
|
9
|
+
console.log("🔧 Instalando servicio systemd para Hive...\n");
|
|
10
|
+
|
|
11
|
+
if (!fs.existsSync(SYSTEMD_DIR)) {
|
|
12
|
+
fs.mkdirSync(SYSTEMD_DIR, { recursive: true });
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const serviceContent = `[Unit]
|
|
16
|
+
Description=Hive Personal AI Gateway
|
|
17
|
+
After=network-online.target
|
|
18
|
+
Wants=network-online.target
|
|
19
|
+
|
|
20
|
+
[Service]
|
|
21
|
+
Type=simple
|
|
22
|
+
ExecStart=${HOME}/.bun/bin/hive start
|
|
23
|
+
ExecStop=${HOME}/.bun/bin/hive stop
|
|
24
|
+
Restart=on-failure
|
|
25
|
+
RestartSec=5
|
|
26
|
+
Environment=PATH=${HOME}/.bun/bin:${HOME}/.npm-global/bin:/usr/local/bin:/usr/bin:/bin
|
|
27
|
+
WorkingDirectory=${HOME}
|
|
28
|
+
|
|
29
|
+
[Install]
|
|
30
|
+
WantedBy=default.target
|
|
31
|
+
`;
|
|
32
|
+
|
|
33
|
+
const servicePath = path.join(SYSTEMD_DIR, "hive.service");
|
|
34
|
+
|
|
35
|
+
fs.writeFileSync(servicePath, serviceContent, "utf-8");
|
|
36
|
+
console.log(`✅ Archivo de servicio creado: ${servicePath}\n`);
|
|
37
|
+
|
|
38
|
+
console.log("Recargando systemd...");
|
|
39
|
+
execSync("systemctl --user daemon-reload", { stdio: "inherit" });
|
|
40
|
+
|
|
41
|
+
console.log("Habilitando servicio...");
|
|
42
|
+
execSync("systemctl --user enable hive", { stdio: "inherit" });
|
|
43
|
+
|
|
44
|
+
console.log("\n✅ Servicio instalado correctamente.\n");
|
|
45
|
+
console.log("Comandos disponibles:");
|
|
46
|
+
console.log(" systemctl --user start hive # Iniciar");
|
|
47
|
+
console.log(" systemctl --user stop hive # Detener");
|
|
48
|
+
console.log(" systemctl --user status hive # Ver estado");
|
|
49
|
+
console.log(" journalctl --user -u hive -f # Ver logs");
|
|
50
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
|
|
4
|
+
const HIVE_DIR = path.join(process.env.HOME || "", ".hive");
|
|
5
|
+
const SESSIONS_DIR = path.join(HIVE_DIR, "sessions");
|
|
6
|
+
|
|
7
|
+
export async function sessions(subcommand: string | undefined, args: string[]): Promise<void> {
|
|
8
|
+
switch (subcommand) {
|
|
9
|
+
case "list":
|
|
10
|
+
await listSessions();
|
|
11
|
+
break;
|
|
12
|
+
case "view":
|
|
13
|
+
await viewSession(args[0]);
|
|
14
|
+
break;
|
|
15
|
+
case "prune":
|
|
16
|
+
await pruneSessions();
|
|
17
|
+
break;
|
|
18
|
+
default:
|
|
19
|
+
console.log(`
|
|
20
|
+
Usage: hive sessions <command>
|
|
21
|
+
|
|
22
|
+
Commands:
|
|
23
|
+
list Listar sesiones
|
|
24
|
+
view <id> Ver transcripción
|
|
25
|
+
prune Eliminar sesiones inactivas
|
|
26
|
+
`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function listSessions(): Promise<void> {
|
|
31
|
+
if (!fs.existsSync(SESSIONS_DIR)) {
|
|
32
|
+
console.log("No hay sesiones");
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const sessions = fs.readdirSync(SESSIONS_DIR).filter((f) => f.endsWith(".json"));
|
|
37
|
+
|
|
38
|
+
if (sessions.length === 0) {
|
|
39
|
+
console.log("No hay sesiones");
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
console.log("\n📋 Sesiones:\n");
|
|
44
|
+
|
|
45
|
+
for (const session of sessions) {
|
|
46
|
+
const sessionPath = path.join(SESSIONS_DIR, session);
|
|
47
|
+
const stat = fs.statSync(sessionPath);
|
|
48
|
+
const id = session.replace(".json", "");
|
|
49
|
+
const date = stat.mtime.toLocaleDateString();
|
|
50
|
+
const time = stat.mtime.toLocaleTimeString();
|
|
51
|
+
|
|
52
|
+
let messageCount = 0;
|
|
53
|
+
try {
|
|
54
|
+
const content = JSON.parse(fs.readFileSync(sessionPath, "utf-8"));
|
|
55
|
+
messageCount = content.messages?.length || 0;
|
|
56
|
+
} catch {
|
|
57
|
+
// ignore
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
console.log(` ${id}`);
|
|
61
|
+
console.log(` Última actividad: ${date} ${time}`);
|
|
62
|
+
console.log(` Mensajes: ${messageCount}`);
|
|
63
|
+
console.log();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function viewSession(id: string | undefined): Promise<void> {
|
|
68
|
+
if (!id) {
|
|
69
|
+
console.log("❌ Especifica el ID de la sesión: hive sessions view <id>");
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const sessionPath = path.join(SESSIONS_DIR, `${id}.json`);
|
|
74
|
+
|
|
75
|
+
if (!fs.existsSync(sessionPath)) {
|
|
76
|
+
console.log(`❌ Sesión no encontrada: ${id}`);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const content = JSON.parse(fs.readFileSync(sessionPath, "utf-8"));
|
|
81
|
+
const messages = content.messages || [];
|
|
82
|
+
|
|
83
|
+
console.log(`\n📜 Sesión: ${id}\n`);
|
|
84
|
+
|
|
85
|
+
for (const msg of messages) {
|
|
86
|
+
const role = msg.role === "user" ? "👤 Usuario" : "🤖 Agente";
|
|
87
|
+
console.log(`${role}:`);
|
|
88
|
+
console.log(` ${msg.content}`);
|
|
89
|
+
console.log();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function pruneSessions(): Promise<void> {
|
|
94
|
+
if (!fs.existsSync(SESSIONS_DIR)) {
|
|
95
|
+
console.log("No hay sesiones para limpiar");
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const sessions = fs.readdirSync(SESSIONS_DIR).filter((f) => f.endsWith(".json"));
|
|
100
|
+
const now = Date.now();
|
|
101
|
+
const maxAge = 7 * 24 * 60 * 60 * 1000; // 7 días
|
|
102
|
+
|
|
103
|
+
let pruned = 0;
|
|
104
|
+
|
|
105
|
+
for (const session of sessions) {
|
|
106
|
+
const sessionPath = path.join(SESSIONS_DIR, session);
|
|
107
|
+
const stat = fs.statSync(sessionPath);
|
|
108
|
+
|
|
109
|
+
if (now - stat.mtimeMs > maxAge) {
|
|
110
|
+
fs.unlinkSync(sessionPath);
|
|
111
|
+
pruned++;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
console.log(`✅ Sesiones eliminadas: ${pruned}`);
|
|
116
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import * as p from "@clack/prompts";
|
|
2
|
+
import * as fs from "fs";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
|
|
5
|
+
const SKILLS_DIR = path.join(process.env.HOME || "", ".hive", "skills");
|
|
6
|
+
const BUNDLED_SKILLS: Array<{ slug: string; name: string; description: string }> = [
|
|
7
|
+
{ slug: "web-search", name: "Web Search", description: "Search the web using multiple search engines" },
|
|
8
|
+
{ slug: "code-exec", name: "Code Execution", description: "Execute code snippets safely" },
|
|
9
|
+
{ slug: "file-ops", name: "File Operations", description: "Read, write, and manage files" },
|
|
10
|
+
{ slug: "memory", name: "Memory", description: "Persistent memory and notes" },
|
|
11
|
+
{ slug: "cron", name: "Cron Jobs", description: "Schedule recurring tasks" },
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
export async function skills(subcommand: string | undefined, args: string[]): Promise<void> {
|
|
15
|
+
switch (subcommand) {
|
|
16
|
+
case "list":
|
|
17
|
+
await listSkills();
|
|
18
|
+
break;
|
|
19
|
+
case "search":
|
|
20
|
+
await searchSkills(args[0]);
|
|
21
|
+
break;
|
|
22
|
+
case "install":
|
|
23
|
+
await installSkill(args[0]);
|
|
24
|
+
break;
|
|
25
|
+
case "remove":
|
|
26
|
+
await removeSkill(args[0]);
|
|
27
|
+
break;
|
|
28
|
+
case "update":
|
|
29
|
+
await updateSkills();
|
|
30
|
+
break;
|
|
31
|
+
default:
|
|
32
|
+
console.log(`
|
|
33
|
+
Usage: hive skills <command>
|
|
34
|
+
|
|
35
|
+
Commands:
|
|
36
|
+
list Listar skills instaladas
|
|
37
|
+
search <query> Buscar skills
|
|
38
|
+
install <slug> Instalar skill
|
|
39
|
+
remove <nombre> Eliminar skill
|
|
40
|
+
update Actualizar skills
|
|
41
|
+
`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function listSkills(): Promise<void> {
|
|
46
|
+
console.log("\n📚 Skills instaladas:\n");
|
|
47
|
+
|
|
48
|
+
console.log(" Bundled (incluidas):");
|
|
49
|
+
for (const skill of BUNDLED_SKILLS) {
|
|
50
|
+
console.log(` • ${skill.name} (${skill.slug})`);
|
|
51
|
+
console.log(` ${skill.description}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (fs.existsSync(SKILLS_DIR)) {
|
|
55
|
+
const managedSkills = fs.readdirSync(SKILLS_DIR).filter((f) => {
|
|
56
|
+
const skillPath = path.join(SKILLS_DIR, f);
|
|
57
|
+
return fs.statSync(skillPath).isDirectory();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
if (managedSkills.length > 0) {
|
|
61
|
+
console.log("\n Managed (instaladas):");
|
|
62
|
+
for (const slug of managedSkills) {
|
|
63
|
+
const skillPath = path.join(SKILLS_DIR, slug, "skill.json");
|
|
64
|
+
if (fs.existsSync(skillPath)) {
|
|
65
|
+
const meta = JSON.parse(fs.readFileSync(skillPath, "utf-8"));
|
|
66
|
+
console.log(` • ${meta.name || slug}`);
|
|
67
|
+
console.log(` ${meta.description || "Sin descripción"}`);
|
|
68
|
+
} else {
|
|
69
|
+
console.log(` • ${slug}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
console.log();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function searchSkills(query: string | undefined): Promise<void> {
|
|
79
|
+
if (!query) {
|
|
80
|
+
console.log("❌ Especifica un término de búsqueda: hive skills search <query>");
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
console.log(`\n🔍 Buscando: "${query}"\n`);
|
|
85
|
+
|
|
86
|
+
const results = BUNDLED_SKILLS.filter(
|
|
87
|
+
(s) =>
|
|
88
|
+
s.name.toLowerCase().includes(query.toLowerCase()) ||
|
|
89
|
+
s.slug.toLowerCase().includes(query.toLowerCase()) ||
|
|
90
|
+
s.description.toLowerCase().includes(query.toLowerCase())
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
if (results.length === 0) {
|
|
94
|
+
console.log("No se encontraron skills");
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
for (const skill of results) {
|
|
99
|
+
console.log(` ${skill.slug}`);
|
|
100
|
+
console.log(` ${skill.name}`);
|
|
101
|
+
console.log(` ${skill.description}`);
|
|
102
|
+
console.log(` Tipo: bundled`);
|
|
103
|
+
console.log();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function installSkill(slug: string | undefined): Promise<void> {
|
|
108
|
+
if (!slug) {
|
|
109
|
+
const result = await p.text({
|
|
110
|
+
message: "Slug de la skill a instalar:",
|
|
111
|
+
placeholder: "example-skill",
|
|
112
|
+
validate: (v) => (!v ? "El slug es requerido" : undefined),
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
if (p.isCancel(result)) {
|
|
116
|
+
p.cancel("Cancelado");
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
slug = result;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
console.log(`\n📦 Instalando skill: ${slug}`);
|
|
123
|
+
|
|
124
|
+
if (!fs.existsSync(SKILLS_DIR)) {
|
|
125
|
+
fs.mkdirSync(SKILLS_DIR, { recursive: true });
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const skillPath = path.join(SKILLS_DIR, slug);
|
|
129
|
+
if (fs.existsSync(skillPath)) {
|
|
130
|
+
console.log("⚠️ La skill ya está instalada");
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
fs.mkdirSync(skillPath, { recursive: true });
|
|
135
|
+
|
|
136
|
+
const meta = {
|
|
137
|
+
name: slug,
|
|
138
|
+
version: "1.0.0",
|
|
139
|
+
description: "Skill instalada manualmente",
|
|
140
|
+
installed: new Date().toISOString(),
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
fs.writeFileSync(path.join(skillPath, "skill.json"), JSON.stringify(meta, null, 2));
|
|
144
|
+
|
|
145
|
+
console.log(`✅ Skill "${slug}" instalada`);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async function removeSkill(slug: string | undefined): Promise<void> {
|
|
149
|
+
if (!slug) {
|
|
150
|
+
console.log("❌ Especifica la skill: hive skills remove <slug>");
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const skillPath = path.join(SKILLS_DIR, slug);
|
|
155
|
+
|
|
156
|
+
if (!fs.existsSync(skillPath)) {
|
|
157
|
+
console.log(`❌ La skill "${slug}" no está instalada`);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const confirm = await p.confirm({
|
|
162
|
+
message: `¿Eliminar la skill "${slug}"?`,
|
|
163
|
+
initialValue: false,
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
if (p.isCancel(confirm) || !confirm) {
|
|
167
|
+
console.log("Cancelado");
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
fs.rmSync(skillPath, { recursive: true });
|
|
172
|
+
console.log(`✅ Skill "${slug}" eliminada`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async function updateSkills(): Promise<void> {
|
|
176
|
+
if (!fs.existsSync(SKILLS_DIR)) {
|
|
177
|
+
console.log("No hay skills instaladas para actualizar");
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const spinner = p.spinner();
|
|
182
|
+
spinner.start("Actualizando skills...");
|
|
183
|
+
|
|
184
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
185
|
+
|
|
186
|
+
spinner.stop("Skills actualizadas ✅");
|
|
187
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { execSync } from "child_process";
|
|
2
|
+
|
|
3
|
+
const PACKAGES = [
|
|
4
|
+
"@johpaz/hive-cli",
|
|
5
|
+
"@johpaz/hive-core",
|
|
6
|
+
"@johpaz/hive-sdk",
|
|
7
|
+
"@johpaz/hive-mcp",
|
|
8
|
+
"@johpaz/hive-skills",
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
export async function update(): Promise<void> {
|
|
12
|
+
console.log("🔄 Actualizando Hive...\n");
|
|
13
|
+
|
|
14
|
+
for (const pkg of PACKAGES) {
|
|
15
|
+
console.log(`Actualizando ${pkg}...`);
|
|
16
|
+
try {
|
|
17
|
+
execSync(`npm install -g ${pkg}@latest`, { stdio: "inherit" });
|
|
18
|
+
console.log(`✅ ${pkg} actualizado\n`);
|
|
19
|
+
} catch (e) {
|
|
20
|
+
console.log(`⚠️ Error actualizando ${pkg}: ${(e as Error).message}\n`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
console.log("✅ Hive actualizado. Ejecuta 'hive --version' para verificar.");
|
|
25
|
+
}
|