@johpaz/hive-cli 1.0.3 → 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 +148 -73
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@johpaz/hive-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "Hive CLI — Command line interface for the Hive AI Gateway",
|
|
5
5
|
"bin": {
|
|
6
6
|
"hive": "src/index.ts"
|
|
@@ -14,7 +14,9 @@
|
|
|
14
14
|
"test": "bun test"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@
|
|
17
|
+
"@clack/prompts": "^1.0.1",
|
|
18
|
+
"@johpaz/hive-core": "^1.0.4",
|
|
19
|
+
"js-yaml": "latest"
|
|
18
20
|
},
|
|
19
21
|
"devDependencies": {
|
|
20
22
|
"typescript": "latest",
|
|
@@ -0,0 +1,187 @@
|
|
|
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
|
+
|
|
6
|
+
const CONFIG_PATH = path.join(process.env.HOME || "", ".hive", "hive.yaml");
|
|
7
|
+
|
|
8
|
+
interface AgentConfig {
|
|
9
|
+
id: string;
|
|
10
|
+
name: string;
|
|
11
|
+
default?: boolean;
|
|
12
|
+
workspace: string;
|
|
13
|
+
agentDir: string;
|
|
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 agents(subcommand: string | undefined, args: string[]): Promise<void> {
|
|
27
|
+
switch (subcommand) {
|
|
28
|
+
case "add":
|
|
29
|
+
await addAgent(args[0]);
|
|
30
|
+
break;
|
|
31
|
+
case "list":
|
|
32
|
+
await listAgents(args.includes("--bindings"));
|
|
33
|
+
break;
|
|
34
|
+
case "remove":
|
|
35
|
+
await removeAgent(args[0]);
|
|
36
|
+
break;
|
|
37
|
+
default:
|
|
38
|
+
console.log(`
|
|
39
|
+
Usage: hive agents <command>
|
|
40
|
+
|
|
41
|
+
Commands:
|
|
42
|
+
add <id> Crear nuevo agente
|
|
43
|
+
list [--bindings] Listar agentes
|
|
44
|
+
remove <id> Eliminar agente
|
|
45
|
+
`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function addAgent(id: string | undefined): Promise<void> {
|
|
50
|
+
const config = loadConfig();
|
|
51
|
+
if (!config) {
|
|
52
|
+
console.log("❌ No hay configuración. Ejecuta: hive onboard");
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (!id) {
|
|
57
|
+
const result = await p.text({
|
|
58
|
+
message: "ID del nuevo agente:",
|
|
59
|
+
placeholder: "work",
|
|
60
|
+
validate: (v) => (!v ? "El ID es requerido" : undefined),
|
|
61
|
+
});
|
|
62
|
+
if (p.isCancel(result)) {
|
|
63
|
+
p.cancel("Cancelado");
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
id = result;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const agentsList = ((config.agents as Record<string, unknown>)?.list as AgentConfig[]) || [];
|
|
70
|
+
if (agentsList.find((a) => a.id === id)) {
|
|
71
|
+
console.log(`❌ Ya existe un agente con ID "${id}"`);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const name = await p.text({
|
|
76
|
+
message: "Nombre del agente:",
|
|
77
|
+
placeholder: id,
|
|
78
|
+
defaultValue: id,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
if (p.isCancel(name)) {
|
|
82
|
+
p.cancel("Cancelado");
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const workspace = path.join(process.env.HOME || "", ".hive", "agents", id as string, "workspace");
|
|
87
|
+
const agentDir = path.join(process.env.HOME || "", ".hive", "agents", id as string, "agent");
|
|
88
|
+
|
|
89
|
+
const newAgent: AgentConfig = {
|
|
90
|
+
id: id as string,
|
|
91
|
+
name: name as string,
|
|
92
|
+
workspace,
|
|
93
|
+
agentDir,
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
fs.mkdirSync(workspace, { recursive: true });
|
|
97
|
+
fs.mkdirSync(agentDir, { recursive: true });
|
|
98
|
+
|
|
99
|
+
const soulPath = path.join(agentDir, "SOUL.md");
|
|
100
|
+
if (!fs.existsSync(soulPath)) {
|
|
101
|
+
fs.writeFileSync(soulPath, `# ${name} — Soul\n\nEres ${name}, un agente especializado.\n`, "utf-8");
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const userPath = path.join(agentDir, "USER.md");
|
|
105
|
+
if (!fs.existsSync(userPath)) {
|
|
106
|
+
fs.writeFileSync(userPath, `# User Profile\n\nNotas sobre el usuario.\n`, "utf-8");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
agentsList.push(newAgent);
|
|
110
|
+
if (!config.agents) config.agents = {};
|
|
111
|
+
(config.agents as Record<string, unknown>).list = agentsList;
|
|
112
|
+
saveConfig(config);
|
|
113
|
+
|
|
114
|
+
console.log(`✅ Agente "${id}" creado`);
|
|
115
|
+
console.log(` Workspace: ${workspace}`);
|
|
116
|
+
console.log(` AgentDir: ${agentDir}`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function listAgents(showBindings: boolean): Promise<void> {
|
|
120
|
+
const config = loadConfig();
|
|
121
|
+
if (!config) {
|
|
122
|
+
console.log("❌ No hay configuración. Ejecuta: hive onboard");
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const agentsList = ((config.agents as Record<string, unknown>)?.list as AgentConfig[]) || [];
|
|
127
|
+
|
|
128
|
+
if (agentsList.length === 0) {
|
|
129
|
+
console.log("No hay agentes configurados");
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
console.log("\n🐝 Agentes:\n");
|
|
134
|
+
for (const agent of agentsList) {
|
|
135
|
+
const defaultTag = agent.default ? " (default)" : "";
|
|
136
|
+
console.log(` ${agent.id}${defaultTag}`);
|
|
137
|
+
console.log(` Nombre: ${agent.name}`);
|
|
138
|
+
console.log(` Workspace: ${agent.workspace}`);
|
|
139
|
+
if (showBindings) {
|
|
140
|
+
const bindings = (config.bindings as Array<{ agent: string }>)?.filter((b) => b.agent === agent.id) || [];
|
|
141
|
+
console.log(` Bindings: ${bindings.length}`);
|
|
142
|
+
}
|
|
143
|
+
console.log();
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function removeAgent(id: string | undefined): Promise<void> {
|
|
148
|
+
if (!id) {
|
|
149
|
+
console.log("❌ Especifica el ID del agente: hive agents remove <id>");
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const config = loadConfig();
|
|
154
|
+
if (!config) {
|
|
155
|
+
console.log("❌ No hay configuración.");
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const agentsList = ((config.agents as Record<string, unknown>)?.list as AgentConfig[]) || [];
|
|
160
|
+
const agent = agentsList.find((a) => a.id === id);
|
|
161
|
+
|
|
162
|
+
if (!agent) {
|
|
163
|
+
console.log(`❌ No existe el agente "${id}"`);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (agent.default) {
|
|
168
|
+
console.log("❌ No puedes eliminar el agente por defecto");
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const confirm = await p.confirm({
|
|
173
|
+
message: `¿Eliminar el agente "${id}"?`,
|
|
174
|
+
initialValue: false,
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
if (p.isCancel(confirm) || !confirm) {
|
|
178
|
+
console.log("Cancelado");
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const newAgentsList = agentsList.filter((a) => a.id !== id);
|
|
183
|
+
(config.agents as Record<string, unknown>).list = newAgentsList;
|
|
184
|
+
saveConfig(config);
|
|
185
|
+
|
|
186
|
+
console.log(`✅ Agente "${id}" eliminado`);
|
|
187
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
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
|
+
import * as readline from "readline";
|
|
7
|
+
|
|
8
|
+
const CONFIG_PATH = path.join(process.env.HOME || "", ".hive", "hive.yaml");
|
|
9
|
+
|
|
10
|
+
function loadConfig(): Record<string, unknown> | null {
|
|
11
|
+
if (!fs.existsSync(CONFIG_PATH)) return null;
|
|
12
|
+
const content = fs.readFileSync(CONFIG_PATH, "utf-8");
|
|
13
|
+
return yaml.load(content) as Record<string, unknown>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function chat(flags: string[]): Promise<void> {
|
|
17
|
+
const config = loadConfig();
|
|
18
|
+
if (!config) {
|
|
19
|
+
console.log("❌ No hay configuración. Ejecuta: hive onboard");
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const agentId = flags.find((f) => f.startsWith("--agent"))?.split("=")[1] ||
|
|
24
|
+
flags[flags.indexOf("--agent") + 1] ||
|
|
25
|
+
"main";
|
|
26
|
+
|
|
27
|
+
console.log(`\n🐝 Chat con agente: ${agentId}`);
|
|
28
|
+
console.log(" Escribe /exit para salir, /new para nueva sesión\n");
|
|
29
|
+
|
|
30
|
+
const rl = readline.createInterface({
|
|
31
|
+
input: process.stdin,
|
|
32
|
+
output: process.stdout,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const question = (prompt: string): Promise<string> => {
|
|
36
|
+
return new Promise((resolve) => {
|
|
37
|
+
rl.question(prompt, resolve);
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const messages: Array<{ role: string; content: string }> = [];
|
|
42
|
+
|
|
43
|
+
while (true) {
|
|
44
|
+
const input = await question("> ");
|
|
45
|
+
const trimmed = input.trim();
|
|
46
|
+
|
|
47
|
+
if (!trimmed) continue;
|
|
48
|
+
|
|
49
|
+
if (trimmed === "/exit" || trimmed === "/quit") {
|
|
50
|
+
console.log("👋 ¡Hasta luego!");
|
|
51
|
+
rl.close();
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (trimmed === "/new") {
|
|
56
|
+
messages.length = 0;
|
|
57
|
+
console.log("📝 Nueva sesión iniciada\n");
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (trimmed === "/help") {
|
|
62
|
+
console.log(`
|
|
63
|
+
Comandos disponibles:
|
|
64
|
+
/exit, /quit - Salir del chat
|
|
65
|
+
/new - Iniciar nueva sesión
|
|
66
|
+
/help - Mostrar esta ayuda
|
|
67
|
+
`);
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
messages.push({ role: "user", content: trimmed });
|
|
72
|
+
|
|
73
|
+
console.log("\n🤖 Pensando...\n");
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
const response = await callLLM(config, messages);
|
|
77
|
+
messages.push({ role: "assistant", content: response });
|
|
78
|
+
console.log(`\n${response}\n`);
|
|
79
|
+
} catch (error) {
|
|
80
|
+
console.log(`❌ Error: ${(error as Error).message}\n`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function callLLM(config: Record<string, unknown>, messages: Array<{ role: string; content: string }>): Promise<string> {
|
|
86
|
+
const models = config.models as Record<string, unknown> | undefined;
|
|
87
|
+
const provider = models?.defaultProvider as string || "anthropic";
|
|
88
|
+
const defaults = models?.defaults as Record<string, string> | undefined;
|
|
89
|
+
const model = defaults?.default || defaults?.[provider] || "claude-sonnet-4-5";
|
|
90
|
+
const providers = models?.providers as Record<string, Record<string, unknown>> | undefined;
|
|
91
|
+
const providerConfig = providers?.[provider] as Record<string, unknown> | undefined;
|
|
92
|
+
const apiKey = providerConfig?.apiKey as string || process.env.ANTHROPIC_API_KEY || "";
|
|
93
|
+
|
|
94
|
+
if (provider === "anthropic") {
|
|
95
|
+
const response = await fetch("https://api.anthropic.com/v1/messages", {
|
|
96
|
+
method: "POST",
|
|
97
|
+
headers: {
|
|
98
|
+
"Content-Type": "application/json",
|
|
99
|
+
"x-api-key": apiKey,
|
|
100
|
+
"anthropic-version": "2023-06-01",
|
|
101
|
+
"anthropic-dangerous-direct-browser-access": "true",
|
|
102
|
+
},
|
|
103
|
+
body: JSON.stringify({
|
|
104
|
+
model,
|
|
105
|
+
max_tokens: 4096,
|
|
106
|
+
messages: messages.map((m) => ({ role: m.role, content: m.content })),
|
|
107
|
+
}),
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
if (!response.ok) {
|
|
111
|
+
throw new Error(`API error: ${response.status}`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const data = (await response.json()) as { content: Array<{ text: string }> };
|
|
115
|
+
return data.content[0]?.text || "Sin respuesta";
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (provider === "openai") {
|
|
119
|
+
const response = await fetch("https://api.openai.com/v1/chat/completions", {
|
|
120
|
+
method: "POST",
|
|
121
|
+
headers: {
|
|
122
|
+
"Content-Type": "application/json",
|
|
123
|
+
Authorization: `Bearer ${apiKey}`,
|
|
124
|
+
},
|
|
125
|
+
body: JSON.stringify({
|
|
126
|
+
model,
|
|
127
|
+
messages: messages.map((m) => ({ role: m.role, content: m.content })),
|
|
128
|
+
}),
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
if (!response.ok) {
|
|
132
|
+
throw new Error(`API error: ${response.status}`);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const data = (await response.json()) as { choices: Array<{ message: { content: string } }> };
|
|
136
|
+
return data.choices[0]?.message?.content || "Sin respuesta";
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return "Proveedor no soportado en modo chat. Usa 'hive start' para el Gateway completo.";
|
|
140
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
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
|
+
function loadConfig(): Record<string, unknown> | null {
|
|
10
|
+
if (!fs.existsSync(CONFIG_PATH)) return null;
|
|
11
|
+
const content = fs.readFileSync(CONFIG_PATH, "utf-8");
|
|
12
|
+
return yaml.load(content) as Record<string, unknown>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function saveConfig(config: Record<string, unknown>): void {
|
|
16
|
+
fs.writeFileSync(CONFIG_PATH, yaml.dump(config, { lineWidth: -1 }), "utf-8");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function redactSecrets(obj: unknown, depth = 0): unknown {
|
|
20
|
+
if (depth > 10) return obj;
|
|
21
|
+
if (typeof obj !== "object" || obj === null) return obj;
|
|
22
|
+
|
|
23
|
+
const sensitiveKeys = ["apiKey", "token", "botToken", "password", "secret", "key"];
|
|
24
|
+
|
|
25
|
+
if (Array.isArray(obj)) {
|
|
26
|
+
return obj.map((item) => redactSecrets(item, depth + 1));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const result: Record<string, unknown> = {};
|
|
30
|
+
for (const [key, value] of Object.entries(obj as Record<string, unknown>)) {
|
|
31
|
+
if (sensitiveKeys.some((sk) => key.toLowerCase().includes(sk.toLowerCase()))) {
|
|
32
|
+
result[key] = "***REDACTED***";
|
|
33
|
+
} else {
|
|
34
|
+
result[key] = redactSecrets(value, depth + 1);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function config(subcommand: string | undefined, args: string[]): Promise<void> {
|
|
41
|
+
switch (subcommand) {
|
|
42
|
+
case "get":
|
|
43
|
+
await getConfig(args[0]);
|
|
44
|
+
break;
|
|
45
|
+
case "set":
|
|
46
|
+
await setConfig(args[0], args[1]);
|
|
47
|
+
break;
|
|
48
|
+
case "show":
|
|
49
|
+
await showConfig();
|
|
50
|
+
break;
|
|
51
|
+
case "edit":
|
|
52
|
+
await editConfig();
|
|
53
|
+
break;
|
|
54
|
+
default:
|
|
55
|
+
console.log(`
|
|
56
|
+
Usage: hive config <command>
|
|
57
|
+
|
|
58
|
+
Commands:
|
|
59
|
+
get <key> Leer valor de config
|
|
60
|
+
set <key> <value> Escribir valor de config
|
|
61
|
+
show Mostrar config completa
|
|
62
|
+
edit Editar hive.yaml en $EDITOR
|
|
63
|
+
`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function getConfig(key: string | undefined): Promise<void> {
|
|
68
|
+
if (!key) {
|
|
69
|
+
console.log("❌ Especifica la clave: hive config get <key>");
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const config = loadConfig();
|
|
74
|
+
if (!config) {
|
|
75
|
+
console.log("❌ No hay configuración. Ejecuta: hive onboard");
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const keys = key.split(".");
|
|
80
|
+
let value: unknown = config;
|
|
81
|
+
|
|
82
|
+
for (const k of keys) {
|
|
83
|
+
if (typeof value === "object" && value !== null && k in value) {
|
|
84
|
+
value = (value as Record<string, unknown>)[k];
|
|
85
|
+
} else {
|
|
86
|
+
console.log(`❌ Clave no encontrada: ${key}`);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (typeof value === "object") {
|
|
92
|
+
console.log(JSON.stringify(value, null, 2));
|
|
93
|
+
} else {
|
|
94
|
+
console.log(value);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async function setConfig(key: string | undefined, value: string | undefined): Promise<void> {
|
|
99
|
+
if (!key) {
|
|
100
|
+
console.log("❌ Especifica la clave: hive config set <key> <value>");
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const config = loadConfig();
|
|
105
|
+
if (!config) {
|
|
106
|
+
console.log("❌ No hay configuración. Ejecuta: hive onboard");
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const keys = key.split(".");
|
|
111
|
+
let obj: Record<string, unknown> = config;
|
|
112
|
+
|
|
113
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
114
|
+
const k = keys[i];
|
|
115
|
+
if (!(k in obj)) {
|
|
116
|
+
obj[k] = {};
|
|
117
|
+
}
|
|
118
|
+
obj = obj[k] as Record<string, unknown>;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const lastKey = keys[keys.length - 1];
|
|
122
|
+
|
|
123
|
+
let parsedValue: unknown = value;
|
|
124
|
+
if (value === "true") parsedValue = true;
|
|
125
|
+
else if (value === "false") parsedValue = false;
|
|
126
|
+
else if (value && !isNaN(Number(value))) parsedValue = Number(value);
|
|
127
|
+
else if (value?.startsWith("[") || value?.startsWith("{")) {
|
|
128
|
+
try {
|
|
129
|
+
parsedValue = JSON.parse(value as string);
|
|
130
|
+
} catch {
|
|
131
|
+
parsedValue = value;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
obj[lastKey] = parsedValue;
|
|
136
|
+
saveConfig(config);
|
|
137
|
+
|
|
138
|
+
console.log(`✅ ${key} = ${JSON.stringify(parsedValue)}`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async function showConfig(): Promise<void> {
|
|
142
|
+
const config = loadConfig();
|
|
143
|
+
if (!config) {
|
|
144
|
+
console.log("❌ No hay configuración. Ejecuta: hive onboard");
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const redacted = redactSecrets(config);
|
|
149
|
+
console.log(yaml.dump(redacted, { lineWidth: -1 }));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async function editConfig(): Promise<void> {
|
|
153
|
+
if (!fs.existsSync(CONFIG_PATH)) {
|
|
154
|
+
console.log("❌ No hay configuración. Ejecuta: hive onboard");
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const editor = process.env.EDITOR || process.env.VISUAL || "nano";
|
|
159
|
+
|
|
160
|
+
const child = spawn(editor, [CONFIG_PATH], {
|
|
161
|
+
stdio: "inherit",
|
|
162
|
+
shell: true,
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
child.on("exit", () => {
|
|
166
|
+
console.log("\n✅ Configuración editada");
|
|
167
|
+
});
|
|
168
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
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
|
+
|
|
6
|
+
const CONFIG_PATH = path.join(process.env.HOME || "", ".hive", "hive.yaml");
|
|
7
|
+
const CRON_DIR = path.join(process.env.HOME || "", ".hive", "cron");
|
|
8
|
+
const CRON_DB = path.join(CRON_DIR, "jobs.json");
|
|
9
|
+
|
|
10
|
+
interface CronJob {
|
|
11
|
+
id: string;
|
|
12
|
+
schedule: string;
|
|
13
|
+
command: string;
|
|
14
|
+
enabled: boolean;
|
|
15
|
+
lastRun?: string;
|
|
16
|
+
nextRun?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function loadJobs(): CronJob[] {
|
|
20
|
+
if (!fs.existsSync(CRON_DB)) return [];
|
|
21
|
+
return JSON.parse(fs.readFileSync(CRON_DB, "utf-8"));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function saveJobs(jobs: CronJob[]): void {
|
|
25
|
+
if (!fs.existsSync(CRON_DIR)) {
|
|
26
|
+
fs.mkdirSync(CRON_DIR, { recursive: true });
|
|
27
|
+
}
|
|
28
|
+
fs.writeFileSync(CRON_DB, JSON.stringify(jobs, null, 2));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function cron(subcommand: string | undefined, args: string[]): Promise<void> {
|
|
32
|
+
switch (subcommand) {
|
|
33
|
+
case "list":
|
|
34
|
+
await listCron();
|
|
35
|
+
break;
|
|
36
|
+
case "add":
|
|
37
|
+
await addCron();
|
|
38
|
+
break;
|
|
39
|
+
case "remove":
|
|
40
|
+
await removeCron(args[0]);
|
|
41
|
+
break;
|
|
42
|
+
case "logs":
|
|
43
|
+
await cronLogs();
|
|
44
|
+
break;
|
|
45
|
+
default:
|
|
46
|
+
console.log(`
|
|
47
|
+
Usage: hive cron <command>
|
|
48
|
+
|
|
49
|
+
Commands:
|
|
50
|
+
list Listar cron jobs
|
|
51
|
+
add Añadir cron job
|
|
52
|
+
remove <id> Eliminar cron job
|
|
53
|
+
logs Ver logs de cron
|
|
54
|
+
`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function listCron(): Promise<void> {
|
|
59
|
+
const jobs = loadJobs();
|
|
60
|
+
|
|
61
|
+
if (jobs.length === 0) {
|
|
62
|
+
console.log("No hay cron jobs configurados");
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
console.log("\n⏰ Cron Jobs:\n");
|
|
67
|
+
|
|
68
|
+
for (const job of jobs) {
|
|
69
|
+
const status = job.enabled ? "✅ Activo" : "⏸️ Pausado";
|
|
70
|
+
console.log(` ${job.id}`);
|
|
71
|
+
console.log(` Estado: ${status}`);
|
|
72
|
+
console.log(` Schedule: ${job.schedule}`);
|
|
73
|
+
console.log(` Comando: ${job.command}`);
|
|
74
|
+
if (job.lastRun) {
|
|
75
|
+
console.log(` Última: ${job.lastRun}`);
|
|
76
|
+
}
|
|
77
|
+
console.log();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function addCron(): Promise<void> {
|
|
82
|
+
const id = await p.text({
|
|
83
|
+
message: "ID del cron job:",
|
|
84
|
+
placeholder: "daily-report",
|
|
85
|
+
validate: (v) => (!v ? "El ID es requerido" : undefined),
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
if (p.isCancel(id)) {
|
|
89
|
+
p.cancel("Cancelado");
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const schedule = await p.text({
|
|
94
|
+
message: "Schedule (cron expression):",
|
|
95
|
+
placeholder: "0 9 * * *",
|
|
96
|
+
validate: (v) => (!v ? "El schedule es requerido" : undefined),
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
if (p.isCancel(schedule)) {
|
|
100
|
+
p.cancel("Cancelado");
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const command = await p.text({
|
|
105
|
+
message: "Comando a ejecutar:",
|
|
106
|
+
placeholder: "hive chat --agent work 'Genera el reporte diario'",
|
|
107
|
+
validate: (v) => (!v ? "El comando es requerido" : undefined),
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
if (p.isCancel(command)) {
|
|
111
|
+
p.cancel("Cancelado");
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const jobs = loadJobs();
|
|
116
|
+
jobs.push({
|
|
117
|
+
id: id as string,
|
|
118
|
+
schedule: schedule as string,
|
|
119
|
+
command: command as string,
|
|
120
|
+
enabled: true,
|
|
121
|
+
});
|
|
122
|
+
saveJobs(jobs);
|
|
123
|
+
|
|
124
|
+
console.log(`✅ Cron job "${id}" creado`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function removeCron(id: string | undefined): Promise<void> {
|
|
128
|
+
if (!id) {
|
|
129
|
+
console.log("❌ Especifica el ID: hive cron remove <id>");
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const jobs = loadJobs();
|
|
134
|
+
const index = jobs.findIndex((j) => j.id === id);
|
|
135
|
+
|
|
136
|
+
if (index === -1) {
|
|
137
|
+
console.log(`❌ Cron job no encontrado: ${id}`);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
jobs.splice(index, 1);
|
|
142
|
+
saveJobs(jobs);
|
|
143
|
+
|
|
144
|
+
console.log(`✅ Cron job "${id}" eliminado`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function cronLogs(): Promise<void> {
|
|
148
|
+
const logFile = path.join(CRON_DIR, "cron.log");
|
|
149
|
+
|
|
150
|
+
if (!fs.existsSync(logFile)) {
|
|
151
|
+
console.log("No hay logs de cron");
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const content = fs.readFileSync(logFile, "utf-8");
|
|
156
|
+
console.log(content);
|
|
157
|
+
}
|