@johpaz/hive-cli 1.0.5 → 1.0.7
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 -3
- package/src/commands/gateway.ts +39 -15
- package/src/commands/onboard.ts +592 -30
- package/src/index.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@johpaz/hive-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"description": "Hive CLI — Command line interface for the Hive AI Gateway",
|
|
5
5
|
"bin": {
|
|
6
6
|
"hive": "src/index.ts"
|
|
@@ -15,11 +15,12 @@
|
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
17
|
"@clack/prompts": "^1.0.1",
|
|
18
|
-
"@johpaz/hive-core": "^1.0.
|
|
18
|
+
"@johpaz/hive-core": "^1.0.7",
|
|
19
|
+
"@johpaz/hive-orchestrator": "workspace:*",
|
|
19
20
|
"js-yaml": "latest"
|
|
20
21
|
},
|
|
21
22
|
"devDependencies": {
|
|
22
23
|
"typescript": "latest",
|
|
23
24
|
"@types/bun": "latest"
|
|
24
25
|
}
|
|
25
|
-
}
|
|
26
|
+
}
|
package/src/commands/gateway.ts
CHANGED
|
@@ -1,12 +1,21 @@
|
|
|
1
|
-
import { loadConfig, startGateway, logger } from "@johpaz/hive-core";
|
|
1
|
+
import { loadConfig, startGateway, logger, expandConfigPath } from "@johpaz/hive-core";
|
|
2
2
|
import * as p from "@clack/prompts";
|
|
3
3
|
import * as fs from "fs";
|
|
4
4
|
import * as path from "path";
|
|
5
5
|
import { spawn, spawnSync, ChildProcess } from "child_process";
|
|
6
6
|
|
|
7
|
-
const
|
|
7
|
+
const DEFAULT_PID_FILE = path.join(process.env.HOME || "", ".hive", "gateway.pid");
|
|
8
8
|
const LOG_FILE = path.join(process.env.HOME || "", ".hive", "logs", "gateway.log");
|
|
9
9
|
|
|
10
|
+
function getPidFile(): string {
|
|
11
|
+
try {
|
|
12
|
+
const config = loadConfig();
|
|
13
|
+
return expandConfigPath(config.gateway?.pidFile) ?? DEFAULT_PID_FILE;
|
|
14
|
+
} catch {
|
|
15
|
+
return DEFAULT_PID_FILE;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
10
19
|
function ensureLogDir(): void {
|
|
11
20
|
const logDir = path.dirname(LOG_FILE);
|
|
12
21
|
if (!fs.existsSync(logDir)) {
|
|
@@ -15,22 +24,26 @@ function ensureLogDir(): void {
|
|
|
15
24
|
}
|
|
16
25
|
|
|
17
26
|
function isRunning(): boolean {
|
|
18
|
-
|
|
19
|
-
|
|
27
|
+
const pidFile = getPidFile();
|
|
28
|
+
if (!fs.existsSync(pidFile)) return false;
|
|
29
|
+
const pid = parseInt(fs.readFileSync(pidFile, "utf-8").trim(), 10);
|
|
20
30
|
if (isNaN(pid)) return false;
|
|
21
31
|
try {
|
|
22
32
|
process.kill(pid, 0);
|
|
23
33
|
return true;
|
|
24
34
|
} catch {
|
|
25
|
-
|
|
35
|
+
try {
|
|
36
|
+
fs.unlinkSync(pidFile);
|
|
37
|
+
} catch { }
|
|
26
38
|
return false;
|
|
27
39
|
}
|
|
28
40
|
}
|
|
29
41
|
|
|
30
42
|
export async function start(flags: string[]): Promise<void> {
|
|
31
43
|
const daemon = flags.includes("--daemon");
|
|
44
|
+
const skipCheck = flags.includes("--skip-check");
|
|
32
45
|
|
|
33
|
-
if (isRunning()) {
|
|
46
|
+
if (!skipCheck && isRunning()) {
|
|
34
47
|
console.log("⚠️ Hive Gateway ya está corriendo");
|
|
35
48
|
return;
|
|
36
49
|
}
|
|
@@ -51,23 +64,30 @@ export async function start(flags: string[]): Promise<void> {
|
|
|
51
64
|
║ ██║ ██║██║ ╚████╔╝ ███████╗ ║
|
|
52
65
|
║ ╚═╝ ╚═╝╚═╝ ╚═══╝ ╚══════╝ ║
|
|
53
66
|
║ ║
|
|
54
|
-
║ Personal AI Gateway — v1.0.
|
|
67
|
+
║ Personal AI Gateway — v1.0.7 ║
|
|
55
68
|
╚════════════════════════════════════════════╝
|
|
56
69
|
`);
|
|
57
70
|
|
|
58
71
|
if (daemon) {
|
|
59
72
|
ensureLogDir();
|
|
60
|
-
const child = spawn(process.execPath, [process.argv[1] || "", "start"], {
|
|
73
|
+
const child = spawn(process.execPath, [process.argv[1] || "", "start", "--skip-check"], {
|
|
61
74
|
detached: true,
|
|
62
75
|
stdio: ["ignore", fs.openSync(LOG_FILE, "a"), fs.openSync(LOG_FILE, "a")],
|
|
63
76
|
});
|
|
64
77
|
child.unref();
|
|
65
|
-
fs.writeFileSync(
|
|
78
|
+
fs.writeFileSync(getPidFile(), child.pid?.toString() || "");
|
|
66
79
|
console.log(`✅ Hive Gateway iniciado en modo daemon (PID: ${child.pid})`);
|
|
67
80
|
console.log(` Logs: ${LOG_FILE}`);
|
|
68
81
|
return;
|
|
69
82
|
}
|
|
70
83
|
|
|
84
|
+
try {
|
|
85
|
+
// Start Orchestrator sidecar in the same process
|
|
86
|
+
await import("@johpaz/hive-orchestrator");
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.warn(`⚠️ No se pudo iniciar el Orchestrator de Dashboards: ${(error as Error).message}`);
|
|
89
|
+
}
|
|
90
|
+
|
|
71
91
|
await startGateway(config);
|
|
72
92
|
}
|
|
73
93
|
|
|
@@ -77,10 +97,11 @@ export async function stop(): Promise<void> {
|
|
|
77
97
|
return;
|
|
78
98
|
}
|
|
79
99
|
|
|
80
|
-
const
|
|
100
|
+
const pidFile = getPidFile();
|
|
101
|
+
const pid = parseInt(fs.readFileSync(pidFile, "utf-8").trim(), 10);
|
|
81
102
|
try {
|
|
82
103
|
process.kill(pid, "SIGTERM");
|
|
83
|
-
fs.unlinkSync(
|
|
104
|
+
fs.unlinkSync(pidFile);
|
|
84
105
|
console.log("✅ Hive Gateway detenido");
|
|
85
106
|
} catch (e) {
|
|
86
107
|
console.error("❌ Error deteniendo el Gateway:", e);
|
|
@@ -99,20 +120,23 @@ export async function status(flags: string[]): Promise<void> {
|
|
|
99
120
|
}
|
|
100
121
|
|
|
101
122
|
const config = loadConfig();
|
|
123
|
+
const pidFile = getPidFile();
|
|
102
124
|
|
|
103
125
|
console.log(`Estado: ${running ? "✅ Corriendo" : "⏹️ Detenido"}`);
|
|
104
126
|
if (running) {
|
|
105
|
-
const pid = fs.readFileSync(
|
|
127
|
+
const pid = fs.readFileSync(pidFile, "utf-8").trim();
|
|
106
128
|
console.log(`PID: ${pid}`);
|
|
107
129
|
}
|
|
108
130
|
console.log(`Puerto: ${config.gateway?.port || 18790}`);
|
|
109
131
|
console.log(`Host: ${config.gateway?.host || "127.0.0.1"}`);
|
|
110
|
-
|
|
132
|
+
const provider = config.models?.defaultProvider || "no configurado";
|
|
133
|
+
const model = config.models?.defaults?.[provider] || "no configurado";
|
|
134
|
+
console.log(`Modelo: ${provider} / ${model}`);
|
|
111
135
|
console.log(`Config: ${configPath}`);
|
|
112
136
|
console.log(`Logs: ${LOG_FILE}`);
|
|
113
137
|
|
|
114
138
|
if (flags.includes("--json")) {
|
|
115
|
-
console.log("\n" + JSON.stringify({ running, pid: running ? fs.readFileSync(
|
|
139
|
+
console.log("\n" + JSON.stringify({ running, pid: running ? fs.readFileSync(pidFile, "utf-8").trim() : null, config }, null, 2));
|
|
116
140
|
}
|
|
117
141
|
}
|
|
118
142
|
|
|
@@ -122,7 +146,7 @@ export async function reload(): Promise<void> {
|
|
|
122
146
|
return;
|
|
123
147
|
}
|
|
124
148
|
|
|
125
|
-
const pid = parseInt(fs.readFileSync(
|
|
149
|
+
const pid = parseInt(fs.readFileSync(getPidFile(), "utf-8").trim(), 10);
|
|
126
150
|
try {
|
|
127
151
|
process.kill(pid, "SIGHUP");
|
|
128
152
|
console.log("✅ Configuración recargada");
|
package/src/commands/onboard.ts
CHANGED
|
@@ -3,7 +3,7 @@ import * as fs from "fs";
|
|
|
3
3
|
import * as path from "path";
|
|
4
4
|
import * as yaml from "js-yaml";
|
|
5
5
|
|
|
6
|
-
const VERSION = "1.0.
|
|
6
|
+
const VERSION = "1.0.7";
|
|
7
7
|
|
|
8
8
|
const DEFAULT_MODELS: Record<string, string> = {
|
|
9
9
|
anthropic: "claude-sonnet-4-6",
|
|
@@ -87,14 +87,29 @@ const AVAILABLE_MODELS: Record<string, Array<{ value: string; label: string; hin
|
|
|
87
87
|
],
|
|
88
88
|
};
|
|
89
89
|
|
|
90
|
+
const BUNDLED_SKILLS = [
|
|
91
|
+
{ name: "web_search", label: "Web Search", hint: "Buscar en la web", default: true },
|
|
92
|
+
{ name: "shell", label: "Shell", hint: "Ejecutar comandos", default: true },
|
|
93
|
+
{ name: "file_manager", label: "File Manager", hint: "Operaciones de archivos", default: true },
|
|
94
|
+
{ name: "http_client", label: "HTTP Client", hint: "Peticiones HTTP", default: true },
|
|
95
|
+
{ name: "memory", label: "Memory", hint: "Memoria persistente", default: true },
|
|
96
|
+
{ name: "cron_manager", label: "Cron Manager", hint: "Tareas programadas", default: false },
|
|
97
|
+
{ name: "system_notify", label: "System Notify", hint: "Notificaciones desktop", default: false },
|
|
98
|
+
{ name: "browser_automation", label: "Browser Automation", hint: "Automatizar navegador", default: false },
|
|
99
|
+
{ name: "context_compact", label: "Context Compact", hint: "Compactar contexto", default: false },
|
|
100
|
+
];
|
|
101
|
+
|
|
90
102
|
interface OnboardConfig {
|
|
91
103
|
agentName: string;
|
|
104
|
+
userName: string;
|
|
105
|
+
userId: string;
|
|
92
106
|
provider: string;
|
|
93
107
|
model: string;
|
|
94
108
|
apiKey: string;
|
|
95
109
|
channel: string;
|
|
96
110
|
channelToken: string;
|
|
97
111
|
workspace: string;
|
|
112
|
+
skills: string[];
|
|
98
113
|
}
|
|
99
114
|
|
|
100
115
|
function generateToken(): string {
|
|
@@ -106,6 +121,42 @@ function generateToken(): string {
|
|
|
106
121
|
return token;
|
|
107
122
|
}
|
|
108
123
|
|
|
124
|
+
function generateUserId(): string {
|
|
125
|
+
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
126
|
+
let token = "user_";
|
|
127
|
+
for (let i = 0; i < 12; i++) {
|
|
128
|
+
token += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
129
|
+
}
|
|
130
|
+
return token;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async function verifyTelegramToken(token: string): Promise<{ ok: boolean; username?: string }> {
|
|
134
|
+
try {
|
|
135
|
+
const res = await fetch(
|
|
136
|
+
`https://api.telegram.org/bot${token}/getMe`,
|
|
137
|
+
{ signal: AbortSignal.timeout(5000) }
|
|
138
|
+
);
|
|
139
|
+
const data = (await res.json()) as {
|
|
140
|
+
ok: boolean;
|
|
141
|
+
result?: { username: string; first_name: string };
|
|
142
|
+
};
|
|
143
|
+
return {
|
|
144
|
+
ok: data.ok,
|
|
145
|
+
username: data.result?.username,
|
|
146
|
+
};
|
|
147
|
+
} catch {
|
|
148
|
+
return { ok: false };
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function validateDiscordToken(token: string): boolean {
|
|
153
|
+
return token.length >= 50;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function validateSlackToken(token: string): boolean {
|
|
157
|
+
return token.startsWith("xoxb-") || token.startsWith("xoxp-");
|
|
158
|
+
}
|
|
159
|
+
|
|
109
160
|
async function testLLMConnection(provider: string, apiKey: string, model: string): Promise<boolean> {
|
|
110
161
|
if (provider === "ollama") {
|
|
111
162
|
try {
|
|
@@ -229,18 +280,47 @@ async function generateConfig(config: OnboardConfig): Promise<void> {
|
|
|
229
280
|
fs.mkdirSync(hiveDir, { recursive: true });
|
|
230
281
|
}
|
|
231
282
|
|
|
283
|
+
const baseUrlMap: Record<string, string> = {
|
|
284
|
+
gemini: "https://generativelanguage.googleapis.com/v1beta",
|
|
285
|
+
deepseek: "https://api.deepseek.com/v1",
|
|
286
|
+
kimi: "https://api.moonshot.cn/v1",
|
|
287
|
+
ollama: "http://localhost:11434/api",
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
const providersConfig: Record<string, Record<string, unknown>> = {};
|
|
291
|
+
|
|
292
|
+
if (config.provider !== "ollama" && config.apiKey) {
|
|
293
|
+
providersConfig[config.provider] = {
|
|
294
|
+
apiKey: config.apiKey,
|
|
295
|
+
};
|
|
296
|
+
if (baseUrlMap[config.provider]) {
|
|
297
|
+
providersConfig[config.provider].baseUrl = baseUrlMap[config.provider];
|
|
298
|
+
}
|
|
299
|
+
} else if (config.provider === "ollama") {
|
|
300
|
+
providersConfig[config.provider] = {
|
|
301
|
+
baseUrl: baseUrlMap[config.provider],
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
|
|
232
305
|
const configObj: Record<string, unknown> = {
|
|
233
306
|
name: config.agentName,
|
|
234
307
|
version: VERSION,
|
|
308
|
+
user: {
|
|
309
|
+
id: config.userId,
|
|
310
|
+
name: config.userName,
|
|
311
|
+
channels: {},
|
|
312
|
+
},
|
|
235
313
|
gateway: {
|
|
236
|
-
port: 18790,
|
|
237
314
|
host: "127.0.0.1",
|
|
238
|
-
|
|
315
|
+
port: 18790,
|
|
316
|
+
authToken: generateToken(),
|
|
239
317
|
},
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
318
|
+
models: {
|
|
319
|
+
defaultProvider: config.provider,
|
|
320
|
+
defaults: {
|
|
321
|
+
[config.provider]: config.model,
|
|
322
|
+
},
|
|
323
|
+
providers: providersConfig,
|
|
244
324
|
},
|
|
245
325
|
agents: {
|
|
246
326
|
list: [
|
|
@@ -255,13 +335,12 @@ async function generateConfig(config: OnboardConfig): Promise<void> {
|
|
|
255
335
|
},
|
|
256
336
|
channels: {},
|
|
257
337
|
skills: {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
338
|
+
allowBundled: config.skills,
|
|
339
|
+
managedDir: path.join(process.env.HOME || "", ".hive", "skills"),
|
|
340
|
+
hotReload: true,
|
|
261
341
|
},
|
|
262
342
|
sessions: {
|
|
263
343
|
pruneAfterHours: 168,
|
|
264
|
-
pruneInterval: 24,
|
|
265
344
|
},
|
|
266
345
|
logging: {
|
|
267
346
|
level: "info",
|
|
@@ -270,22 +349,13 @@ async function generateConfig(config: OnboardConfig): Promise<void> {
|
|
|
270
349
|
},
|
|
271
350
|
};
|
|
272
351
|
|
|
273
|
-
if (config.provider === "gemini") {
|
|
274
|
-
(configObj.model as Record<string, unknown>).baseUrl = "https://generativelanguage.googleapis.com/v1beta";
|
|
275
|
-
} else if (config.provider === "deepseek") {
|
|
276
|
-
(configObj.model as Record<string, unknown>).baseUrl = "https://api.deepseek.com/v1";
|
|
277
|
-
} else if (config.provider === "kimi") {
|
|
278
|
-
(configObj.model as Record<string, unknown>).baseUrl = "https://api.moonshot.cn/v1";
|
|
279
|
-
} else if (config.provider === "ollama") {
|
|
280
|
-
(configObj.model as Record<string, unknown>).baseUrl = "http://localhost:11434/api";
|
|
281
|
-
}
|
|
282
|
-
|
|
283
352
|
if (config.channel === "telegram" && config.channelToken) {
|
|
284
353
|
configObj.channels = {
|
|
285
354
|
telegram: {
|
|
286
355
|
accounts: {
|
|
287
356
|
default: {
|
|
288
357
|
botToken: config.channelToken,
|
|
358
|
+
dmPolicy: "open",
|
|
289
359
|
},
|
|
290
360
|
},
|
|
291
361
|
},
|
|
@@ -306,7 +376,7 @@ async function generateConfig(config: OnboardConfig): Promise<void> {
|
|
|
306
376
|
fs.chmodSync(configPath, 0o600);
|
|
307
377
|
}
|
|
308
378
|
|
|
309
|
-
async function generateWorkspace(workspace: string, agentName: string, ethicsChoice: string): Promise<void> {
|
|
379
|
+
async function generateWorkspace(workspace: string, agentName: string, userName: string, userId: string, userLanguage: string, userTimezone: string, ethicsChoice: string): Promise<void> {
|
|
310
380
|
if (!fs.existsSync(workspace)) {
|
|
311
381
|
fs.mkdirSync(workspace, { recursive: true });
|
|
312
382
|
}
|
|
@@ -344,16 +414,39 @@ Help the user with their tasks, answer questions, and provide assistance.
|
|
|
344
414
|
if (!fs.existsSync(userPath)) {
|
|
345
415
|
fs.writeFileSync(
|
|
346
416
|
userPath,
|
|
347
|
-
`#
|
|
417
|
+
`# ${userName} — Perfil de Usuario
|
|
418
|
+
|
|
419
|
+
## Identidad
|
|
420
|
+
|
|
421
|
+
- **Nombre:** ${userName}
|
|
422
|
+
- **ID Interno:** \`${userId}\` (este ID es para uso interno del sistema)
|
|
423
|
+
- **Idioma preferido:** ${userLanguage}
|
|
424
|
+
- **Zona horaria:** ${userTimezone}
|
|
425
|
+
|
|
426
|
+
## Canales Vinculados
|
|
427
|
+
|
|
428
|
+
Tu ID único (\`${userId}\`) se usa para unificar conversaciones entre canales.
|
|
429
|
+
|
|
430
|
+
| Canal | ID de Usuario |
|
|
431
|
+
|-------|---------------|
|
|
432
|
+
| Telegram | - |
|
|
433
|
+
| WhatsApp | - |
|
|
434
|
+
| Discord | - |
|
|
435
|
+
| Slack | - |
|
|
436
|
+
|
|
437
|
+
Para vincular un canal, usa:
|
|
438
|
+
\`\`\`bash
|
|
439
|
+
hive config set user.channels.telegram "tu_telegram_id"
|
|
440
|
+
\`\`\`
|
|
348
441
|
|
|
349
|
-
##
|
|
442
|
+
## Preferencias
|
|
350
443
|
|
|
351
|
-
-
|
|
352
|
-
-
|
|
444
|
+
- Idioma: ${userLanguage}
|
|
445
|
+
- Zona horaria: ${userTimezone}
|
|
353
446
|
|
|
354
|
-
##
|
|
447
|
+
## Notas
|
|
355
448
|
|
|
356
|
-
|
|
449
|
+
Notas personales sobre ti.
|
|
357
450
|
`,
|
|
358
451
|
"utf-8"
|
|
359
452
|
);
|
|
@@ -424,7 +517,345 @@ WantedBy=default.target
|
|
|
424
517
|
spawnSync("systemctl", ["--user", "enable", "hive"], { stdio: "inherit" });
|
|
425
518
|
}
|
|
426
519
|
|
|
427
|
-
|
|
520
|
+
async function isGatewayRunning(): Promise<boolean> {
|
|
521
|
+
const pidFile = path.join(process.env.HOME || "", ".hive", "gateway.pid");
|
|
522
|
+
if (!fs.existsSync(pidFile)) return false;
|
|
523
|
+
|
|
524
|
+
try {
|
|
525
|
+
const pid = parseInt(fs.readFileSync(pidFile, "utf-8").trim(), 10);
|
|
526
|
+
process.kill(pid, 0);
|
|
527
|
+
return true;
|
|
528
|
+
} catch {
|
|
529
|
+
return false;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
async function reloadGateway(): Promise<void> {
|
|
534
|
+
const pidFile = path.join(process.env.HOME || "", ".hive", "gateway.pid");
|
|
535
|
+
if (!fs.existsSync(pidFile)) return;
|
|
536
|
+
|
|
537
|
+
try {
|
|
538
|
+
const pid = parseInt(fs.readFileSync(pidFile, "utf-8").trim(), 10);
|
|
539
|
+
process.kill(pid, "SIGHUP");
|
|
540
|
+
} catch {
|
|
541
|
+
// Gateway not running
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
interface ExistingConfig {
|
|
546
|
+
agentName: string;
|
|
547
|
+
provider: string;
|
|
548
|
+
model: string;
|
|
549
|
+
channels: string[];
|
|
550
|
+
apiKey: string;
|
|
551
|
+
workspace: string;
|
|
552
|
+
skills: string[];
|
|
553
|
+
raw: Record<string, unknown>;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
function parseExistingConfig(raw: Record<string, unknown>): ExistingConfig {
|
|
557
|
+
const agents = (raw.agents as Record<string, unknown>)?.list as Array<Record<string, unknown>> | undefined;
|
|
558
|
+
const models = raw.models as Record<string, unknown> | undefined;
|
|
559
|
+
const providers = models?.providers as Record<string, Record<string, unknown>> | undefined;
|
|
560
|
+
const defaultProvider = models?.defaultProvider as string | undefined;
|
|
561
|
+
const providerConfig = providers?.[defaultProvider || ""] as Record<string, unknown> | undefined;
|
|
562
|
+
|
|
563
|
+
return {
|
|
564
|
+
agentName: (agents?.[0]?.name as string) ?? "Hive",
|
|
565
|
+
provider: defaultProvider ?? "gemini",
|
|
566
|
+
model: (models?.defaults as Record<string, string>)?.[defaultProvider || ""] ?? "",
|
|
567
|
+
channels: Object.keys((raw.channels as Record<string, unknown>) ?? {}),
|
|
568
|
+
apiKey: (providerConfig?.apiKey as string) ?? "",
|
|
569
|
+
workspace: (agents?.[0]?.workspace as string) ?? path.join(process.env.HOME || "", ".hive", "workspace"),
|
|
570
|
+
skills: ((raw.skills as Record<string, unknown>)?.allowBundled as string[]) ?? [],
|
|
571
|
+
raw,
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
async function runUpdateWizard(configPath: string, existing: ExistingConfig): Promise<void> {
|
|
576
|
+
p.note(
|
|
577
|
+
"Presiona Enter para mantener el valor actual de cada campo.",
|
|
578
|
+
"✏️ Modo actualización"
|
|
579
|
+
);
|
|
580
|
+
|
|
581
|
+
// Nombre del agente
|
|
582
|
+
const agentName = await p.text({
|
|
583
|
+
message: "Nombre del agente:",
|
|
584
|
+
placeholder: existing.agentName,
|
|
585
|
+
defaultValue: existing.agentName,
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
if (p.isCancel(agentName)) {
|
|
589
|
+
p.cancel("Actualización cancelada.");
|
|
590
|
+
process.exit(0);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// Proveedor LLM
|
|
594
|
+
const changeProvider = await p.confirm({
|
|
595
|
+
message: `Proveedor actual: ${existing.provider} (${existing.model || "no configurado"}). ¿Cambiar?`,
|
|
596
|
+
initialValue: false,
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
if (p.isCancel(changeProvider)) {
|
|
600
|
+
p.cancel("Actualización cancelada.");
|
|
601
|
+
process.exit(0);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
let provider = existing.provider;
|
|
605
|
+
let model = existing.model;
|
|
606
|
+
let apiKey = existing.apiKey;
|
|
607
|
+
|
|
608
|
+
if (changeProvider) {
|
|
609
|
+
provider = await p.select({
|
|
610
|
+
message: "Nuevo proveedor LLM:",
|
|
611
|
+
options: [
|
|
612
|
+
{ value: "anthropic", label: "Anthropic (Claude)", hint: "Recomendado — Claude 4.6" },
|
|
613
|
+
{ value: "openai", label: "OpenAI (GPT-5)", hint: "GPT-5.2" },
|
|
614
|
+
{ value: "gemini", label: "Google Gemini", hint: "Gemini 3 Flash" },
|
|
615
|
+
{ value: "deepseek", label: "DeepSeek", hint: "Muy económico" },
|
|
616
|
+
{ value: "kimi", label: "Kimi (Moonshot AI)", hint: "Contexto largo" },
|
|
617
|
+
{ value: "openrouter", label: "OpenRouter", hint: "Multi-modelo" },
|
|
618
|
+
{ value: "ollama", label: "Ollama (local)", hint: "Sin costo" },
|
|
619
|
+
],
|
|
620
|
+
}) as string;
|
|
621
|
+
|
|
622
|
+
if (p.isCancel(provider)) {
|
|
623
|
+
p.cancel("Actualización cancelada.");
|
|
624
|
+
process.exit(0);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
const models = AVAILABLE_MODELS[provider] || [{ value: DEFAULT_MODELS[provider], label: DEFAULT_MODELS[provider] }];
|
|
628
|
+
|
|
629
|
+
if (models.length > 1) {
|
|
630
|
+
model = await p.select({
|
|
631
|
+
message: `Modelo de ${provider}:`,
|
|
632
|
+
options: models,
|
|
633
|
+
}) as string;
|
|
634
|
+
|
|
635
|
+
if (p.isCancel(model)) {
|
|
636
|
+
p.cancel("Actualización cancelada.");
|
|
637
|
+
process.exit(0);
|
|
638
|
+
}
|
|
639
|
+
} else {
|
|
640
|
+
model = models[0].value;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
if (provider !== "ollama") {
|
|
644
|
+
const link = API_KEY_LINKS[provider];
|
|
645
|
+
if (link) {
|
|
646
|
+
p.note(`Obtén tu API key en:\n${link}`, `API key de ${provider}`);
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
const keyResult = await p.text({
|
|
650
|
+
message: `API key de ${provider}:`,
|
|
651
|
+
placeholder: API_KEY_PLACEHOLDERS[provider] || "sk-...",
|
|
652
|
+
validate: (v: string) => (!v || v.length < 10 ? "La key parece muy corta" : undefined),
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
if (p.isCancel(keyResult)) {
|
|
656
|
+
p.cancel("Actualización cancelada.");
|
|
657
|
+
process.exit(0);
|
|
658
|
+
}
|
|
659
|
+
apiKey = keyResult;
|
|
660
|
+
|
|
661
|
+
const spinner = p.spinner();
|
|
662
|
+
spinner.start(`Verificando conexión con ${provider}...`);
|
|
663
|
+
|
|
664
|
+
const connected = await testLLMConnection(provider, apiKey, model as string);
|
|
665
|
+
|
|
666
|
+
if (!connected) {
|
|
667
|
+
spinner.stop(`❌ Error conectando con ${provider}`);
|
|
668
|
+
p.outro("API key inválida. Ejecuta 'hive onboard' de nuevo con la key correcta.");
|
|
669
|
+
process.exit(1);
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
spinner.stop(`✅ Conexión con ${provider} verificada`);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
// Canales
|
|
677
|
+
const currentChannels = existing.channels.length > 0 ? existing.channels.join(", ") : "ninguno";
|
|
678
|
+
const changeChannel = await p.confirm({
|
|
679
|
+
message: `Canales configurados: ${currentChannels}. ¿Añadir o cambiar?`,
|
|
680
|
+
initialValue: false,
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
if (p.isCancel(changeChannel)) {
|
|
684
|
+
p.cancel("Actualización cancelada.");
|
|
685
|
+
process.exit(0);
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
let channels = existing.raw.channels as Record<string, unknown> | undefined;
|
|
689
|
+
|
|
690
|
+
if (changeChannel) {
|
|
691
|
+
const channel = await p.select({
|
|
692
|
+
message: "Canal a configurar:",
|
|
693
|
+
options: [
|
|
694
|
+
{ value: "telegram", label: "Telegram", hint: "Recomendado" },
|
|
695
|
+
{ value: "discord", label: "Discord" },
|
|
696
|
+
{ value: "webchat", label: "WebChat (local)" },
|
|
697
|
+
{ value: "none", label: "Ninguno" },
|
|
698
|
+
],
|
|
699
|
+
}) as string;
|
|
700
|
+
|
|
701
|
+
if (p.isCancel(channel)) {
|
|
702
|
+
p.cancel("Actualización cancelada.");
|
|
703
|
+
process.exit(0);
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
if (channel === "telegram") {
|
|
707
|
+
p.note(
|
|
708
|
+
"1. Abre Telegram y busca @BotFather\n" +
|
|
709
|
+
"2. Escribe /newbot y sigue las instrucciones\n" +
|
|
710
|
+
"3. Copia el token que te da BotFather",
|
|
711
|
+
"Cómo obtener el token de Telegram"
|
|
712
|
+
);
|
|
713
|
+
|
|
714
|
+
const tokenResult = await p.text({
|
|
715
|
+
message: "Token de Telegram BotFather:",
|
|
716
|
+
placeholder: "123456789:ABCdefGHI...",
|
|
717
|
+
});
|
|
718
|
+
|
|
719
|
+
if (p.isCancel(tokenResult)) {
|
|
720
|
+
p.cancel("Actualización cancelada.");
|
|
721
|
+
process.exit(0);
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
const spinner = p.spinner();
|
|
725
|
+
spinner.start("Verificando token de Telegram...");
|
|
726
|
+
const tg = await verifyTelegramToken(tokenResult as string);
|
|
727
|
+
if (tg.ok && tg.username) {
|
|
728
|
+
spinner.stop(`✅ Bot verificado: @${tg.username}`);
|
|
729
|
+
} else {
|
|
730
|
+
spinner.stop("⚠️ Token no verificado — se guardará de todas formas");
|
|
731
|
+
p.note(
|
|
732
|
+
"El token se guardó pero no pudo verificarse.\n" +
|
|
733
|
+
"Si es incorrecto, ejecuta: hive onboard (opción actualizar)",
|
|
734
|
+
"Aviso"
|
|
735
|
+
);
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
const telegramAccount: Record<string, unknown> = {
|
|
739
|
+
botToken: tokenResult,
|
|
740
|
+
dmPolicy: "open",
|
|
741
|
+
};
|
|
742
|
+
|
|
743
|
+
channels = {
|
|
744
|
+
telegram: {
|
|
745
|
+
accounts: {
|
|
746
|
+
default: telegramAccount,
|
|
747
|
+
},
|
|
748
|
+
},
|
|
749
|
+
};
|
|
750
|
+
} else if (channel === "discord") {
|
|
751
|
+
p.note(
|
|
752
|
+
"1. Ve a https://discord.com/developers/applications\n" +
|
|
753
|
+
"2. Crea una nueva aplicación\n" +
|
|
754
|
+
"3. Ve a Bot → Reset Token\n" +
|
|
755
|
+
"4. Habilita 'Message Content Intent'",
|
|
756
|
+
"Cómo obtener el token de Discord"
|
|
757
|
+
);
|
|
758
|
+
|
|
759
|
+
const tokenResult = await p.text({
|
|
760
|
+
message: "Token del bot de Discord:",
|
|
761
|
+
placeholder: "MTk4NjIyNDgzNDcxO...",
|
|
762
|
+
});
|
|
763
|
+
|
|
764
|
+
if (p.isCancel(tokenResult)) {
|
|
765
|
+
p.cancel("Actualización cancelada.");
|
|
766
|
+
process.exit(0);
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
channels = {
|
|
770
|
+
discord: {
|
|
771
|
+
accounts: {
|
|
772
|
+
default: {
|
|
773
|
+
token: tokenResult,
|
|
774
|
+
},
|
|
775
|
+
},
|
|
776
|
+
},
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
// Guardar cambios
|
|
782
|
+
const spinner = p.spinner();
|
|
783
|
+
spinner.start("Guardando cambios...");
|
|
784
|
+
|
|
785
|
+
const baseUrlMap: Record<string, string> = {
|
|
786
|
+
gemini: "https://generativelanguage.googleapis.com/v1beta",
|
|
787
|
+
deepseek: "https://api.deepseek.com/v1",
|
|
788
|
+
kimi: "https://api.moonshot.cn/v1",
|
|
789
|
+
ollama: "http://localhost:11434/api",
|
|
790
|
+
};
|
|
791
|
+
|
|
792
|
+
const providersConfig: Record<string, Record<string, unknown>> = {};
|
|
793
|
+
if (provider !== "ollama" && apiKey) {
|
|
794
|
+
providersConfig[provider] = { apiKey };
|
|
795
|
+
if (baseUrlMap[provider]) {
|
|
796
|
+
providersConfig[provider].baseUrl = baseUrlMap[provider];
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
const updatedConfig: Record<string, unknown> = {
|
|
801
|
+
...existing.raw,
|
|
802
|
+
name: agentName,
|
|
803
|
+
agents: {
|
|
804
|
+
list: [
|
|
805
|
+
{
|
|
806
|
+
id: "main",
|
|
807
|
+
default: true,
|
|
808
|
+
name: agentName,
|
|
809
|
+
workspace: existing.workspace,
|
|
810
|
+
agentDir: path.join(process.env.HOME || "", ".hive", "agents", "main", "agent"),
|
|
811
|
+
},
|
|
812
|
+
],
|
|
813
|
+
},
|
|
814
|
+
models: {
|
|
815
|
+
defaultProvider: provider,
|
|
816
|
+
defaults: {
|
|
817
|
+
[provider]: model,
|
|
818
|
+
},
|
|
819
|
+
providers: providersConfig,
|
|
820
|
+
},
|
|
821
|
+
};
|
|
822
|
+
|
|
823
|
+
if (channels) {
|
|
824
|
+
updatedConfig.channels = channels;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
fs.writeFileSync(configPath, yaml.dump(updatedConfig, { lineWidth: -1 }), "utf-8");
|
|
828
|
+
fs.chmodSync(configPath, 0o600);
|
|
829
|
+
|
|
830
|
+
spinner.stop("Cambios guardados ✅");
|
|
831
|
+
|
|
832
|
+
// Recargar Gateway si está corriendo
|
|
833
|
+
const running = await isGatewayRunning();
|
|
834
|
+
if (running) {
|
|
835
|
+
const reload = await p.confirm({
|
|
836
|
+
message: "El Gateway está corriendo. ¿Recargar configuración ahora?",
|
|
837
|
+
initialValue: true,
|
|
838
|
+
});
|
|
839
|
+
|
|
840
|
+
if (reload) {
|
|
841
|
+
await reloadGateway();
|
|
842
|
+
p.log.success("Configuración recargada ✅");
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
const channelDisplay = channels ? Object.keys(channels).join(", ") || "ninguno" : existing.channels.join(", ") || "ninguno";
|
|
847
|
+
|
|
848
|
+
p.outro(
|
|
849
|
+
`✅ Configuración actualizada.\n\n` +
|
|
850
|
+
` Agente: ${agentName}\n` +
|
|
851
|
+
` Proveedor: ${provider} (${model})\n` +
|
|
852
|
+
` Canales: ${channelDisplay}\n\n` +
|
|
853
|
+
` hive status → ver estado actual\n` +
|
|
854
|
+
` hive reload → recargar manualmente`
|
|
855
|
+
);
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
async function runFullWizard(configPath: string): Promise<void> {
|
|
428
859
|
p.intro("🐝 Bienvenido a Hive — Personal AI Gateway");
|
|
429
860
|
|
|
430
861
|
const agentName = await p.text({
|
|
@@ -438,6 +869,44 @@ export async function onboard(): Promise<void> {
|
|
|
438
869
|
process.exit(0);
|
|
439
870
|
}
|
|
440
871
|
|
|
872
|
+
const userName = await p.text({
|
|
873
|
+
message: "¿Cómo te llamas? (nombre para identificarte)",
|
|
874
|
+
placeholder: "Usuario",
|
|
875
|
+
defaultValue: "Usuario",
|
|
876
|
+
});
|
|
877
|
+
|
|
878
|
+
if (p.isCancel(userName)) {
|
|
879
|
+
p.cancel("Onboarding cancelado.");
|
|
880
|
+
process.exit(0);
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
const userLanguage = await p.select({
|
|
884
|
+
message: "¿En qué idioma prefieres que te responda?",
|
|
885
|
+
options: [
|
|
886
|
+
{ value: "Spanish", label: "Español" },
|
|
887
|
+
{ value: "English", label: "English" },
|
|
888
|
+
{ value: "Spanish, English", label: "Ambos / Both" },
|
|
889
|
+
],
|
|
890
|
+
});
|
|
891
|
+
|
|
892
|
+
if (p.isCancel(userLanguage)) {
|
|
893
|
+
p.cancel("Onboarding cancelado.");
|
|
894
|
+
process.exit(0);
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
const userTimezone = await p.text({
|
|
898
|
+
message: "¿Cuál es tu zona horaria? (ej: America/Bogota, Europe/Madrid)",
|
|
899
|
+
placeholder: "America/Bogota",
|
|
900
|
+
defaultValue: "America/Bogota",
|
|
901
|
+
});
|
|
902
|
+
|
|
903
|
+
if (p.isCancel(userTimezone)) {
|
|
904
|
+
p.cancel("Onboarding cancelado.");
|
|
905
|
+
process.exit(0);
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
const userId = generateUserId();
|
|
909
|
+
|
|
441
910
|
const provider = await p.select({
|
|
442
911
|
message: "¿Qué proveedor LLM quieres usar?",
|
|
443
912
|
options: [
|
|
@@ -608,6 +1077,23 @@ export async function onboard(): Promise<void> {
|
|
|
608
1077
|
process.exit(0);
|
|
609
1078
|
}
|
|
610
1079
|
channelToken = tokenResult;
|
|
1080
|
+
|
|
1081
|
+
const verifySpinner = p.spinner();
|
|
1082
|
+
verifySpinner.start("Verificando token de Telegram...");
|
|
1083
|
+
const tg = await verifyTelegramToken(channelToken);
|
|
1084
|
+
if (tg.ok && tg.username) {
|
|
1085
|
+
verifySpinner.stop(`✅ Bot verificado: @${tg.username}`);
|
|
1086
|
+
} else {
|
|
1087
|
+
verifySpinner.stop("⚠️ Token no verificado — se guardará de todas formas");
|
|
1088
|
+
p.note(
|
|
1089
|
+
"El token no pudo verificarse. Si es incorrecto, ejecuta 'hive onboard' después.\n\n" +
|
|
1090
|
+
"Para obtener tu Telegram ID y autorizarte:\n" +
|
|
1091
|
+
"1. Inicia el bot con 'hive start'\n" +
|
|
1092
|
+
"2. Escribe /myid al bot\n" +
|
|
1093
|
+
"3. Añade tu ID a ~/.hive/hive.yaml",
|
|
1094
|
+
"Próximos pasos"
|
|
1095
|
+
);
|
|
1096
|
+
}
|
|
611
1097
|
} else if (channel === "discord") {
|
|
612
1098
|
p.note(
|
|
613
1099
|
"1. Ve a https://discord.com/developers/applications\n" +
|
|
@@ -629,6 +1115,23 @@ export async function onboard(): Promise<void> {
|
|
|
629
1115
|
}
|
|
630
1116
|
}
|
|
631
1117
|
|
|
1118
|
+
// Skills selection
|
|
1119
|
+
p.note("Skills son capacidades predefinidas que el agente puede usar.\nActiva las que necesites.", "Skills disponibles");
|
|
1120
|
+
const selectedSkills = await p.multiselect({
|
|
1121
|
+
message: "¿Qué skills deseas activar?",
|
|
1122
|
+
options: BUNDLED_SKILLS.map(s => ({
|
|
1123
|
+
value: s.name,
|
|
1124
|
+
label: s.label,
|
|
1125
|
+
hint: s.hint,
|
|
1126
|
+
})),
|
|
1127
|
+
initialValues: BUNDLED_SKILLS.filter(s => s.default).map(s => s.name),
|
|
1128
|
+
});
|
|
1129
|
+
|
|
1130
|
+
if (p.isCancel(selectedSkills)) {
|
|
1131
|
+
p.cancel("Onboarding cancelado.");
|
|
1132
|
+
process.exit(0);
|
|
1133
|
+
}
|
|
1134
|
+
|
|
632
1135
|
const installService = await p.confirm({
|
|
633
1136
|
message: "¿Instalar Hive como servicio del sistema? (arranca automáticamente)",
|
|
634
1137
|
initialValue: false,
|
|
@@ -644,15 +1147,18 @@ export async function onboard(): Promise<void> {
|
|
|
644
1147
|
|
|
645
1148
|
await generateConfig({
|
|
646
1149
|
agentName: agentName as string,
|
|
1150
|
+
userName: userName as string,
|
|
1151
|
+
userId: userId,
|
|
647
1152
|
provider: providerKey,
|
|
648
1153
|
model: model as string,
|
|
649
1154
|
apiKey,
|
|
650
1155
|
channel: channel as string,
|
|
651
1156
|
channelToken,
|
|
652
1157
|
workspace: workspace as string,
|
|
1158
|
+
skills: selectedSkills as string[],
|
|
653
1159
|
});
|
|
654
1160
|
|
|
655
|
-
await generateWorkspace(workspace as string, agentName as string, ethicsChoice as string);
|
|
1161
|
+
await generateWorkspace(workspace as string, agentName as string, userName as string, userId, userLanguage as string, userTimezone as string, ethicsChoice as string);
|
|
656
1162
|
|
|
657
1163
|
spinner.stop("Configuración creada ✅");
|
|
658
1164
|
|
|
@@ -688,3 +1194,59 @@ export async function onboard(): Promise<void> {
|
|
|
688
1194
|
` hive agents add <nombre>`
|
|
689
1195
|
);
|
|
690
1196
|
}
|
|
1197
|
+
|
|
1198
|
+
export async function onboard(): Promise<void> {
|
|
1199
|
+
const hiveDir = path.join(process.env.HOME || "", ".hive");
|
|
1200
|
+
const configPath = path.join(hiveDir, "hive.yaml");
|
|
1201
|
+
|
|
1202
|
+
if (fs.existsSync(configPath)) {
|
|
1203
|
+
p.intro("🔍 Configuración existente detectada");
|
|
1204
|
+
|
|
1205
|
+
const rawConfig = yaml.load(fs.readFileSync(configPath, "utf-8")) as Record<string, unknown>;
|
|
1206
|
+
const existing = parseExistingConfig(rawConfig);
|
|
1207
|
+
|
|
1208
|
+
p.note(
|
|
1209
|
+
` Agente: ${existing.agentName}\n` +
|
|
1210
|
+
` Proveedor: ${existing.provider} (${existing.model || "no configurado"})\n` +
|
|
1211
|
+
` Canales: ${existing.channels.length > 0 ? existing.channels.join(", ") : "ninguno"}\n` +
|
|
1212
|
+
` Workspace: ${existing.workspace}`,
|
|
1213
|
+
"Config actual"
|
|
1214
|
+
);
|
|
1215
|
+
|
|
1216
|
+
const action = await p.select({
|
|
1217
|
+
message: "¿Qué quieres hacer?",
|
|
1218
|
+
options: [
|
|
1219
|
+
{ value: "update", label: "Actualizar", hint: "Modificar algunos campos" },
|
|
1220
|
+
{ value: "reset", label: "Reiniciar", hint: "Crear config desde cero" },
|
|
1221
|
+
{ value: "cancel", label: "Cancelar", hint: "Salir sin cambios" },
|
|
1222
|
+
],
|
|
1223
|
+
});
|
|
1224
|
+
|
|
1225
|
+
if (p.isCancel(action) || action === "cancel") {
|
|
1226
|
+
p.cancel("Operación cancelada.");
|
|
1227
|
+
process.exit(0);
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
if (action === "reset") {
|
|
1231
|
+
const confirm = await p.confirm({
|
|
1232
|
+
message: "⚠️ Esto borrará tu config actual. ¿Continuar?",
|
|
1233
|
+
initialValue: false,
|
|
1234
|
+
});
|
|
1235
|
+
|
|
1236
|
+
if (p.isCancel(confirm) || !confirm) {
|
|
1237
|
+
p.cancel("Operación cancelada.");
|
|
1238
|
+
process.exit(0);
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
const backupPath = path.join(hiveDir, `hive.yaml.backup.${Date.now()}`);
|
|
1242
|
+
fs.copyFileSync(configPath, backupPath);
|
|
1243
|
+
p.log.info(`Backup creado: ${backupPath}`);
|
|
1244
|
+
|
|
1245
|
+
await runFullWizard(configPath);
|
|
1246
|
+
} else {
|
|
1247
|
+
await runUpdateWizard(configPath, existing);
|
|
1248
|
+
}
|
|
1249
|
+
} else {
|
|
1250
|
+
await runFullWizard(configPath);
|
|
1251
|
+
}
|
|
1252
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -14,7 +14,7 @@ import { securityAudit } from "./commands/security";
|
|
|
14
14
|
import { installService } from "./commands/service";
|
|
15
15
|
import { update } from "./commands/update";
|
|
16
16
|
|
|
17
|
-
const VERSION = "1.0.
|
|
17
|
+
const VERSION = "1.0.7";
|
|
18
18
|
|
|
19
19
|
const HELP = `
|
|
20
20
|
🐝 Hive — Personal AI Gateway v${VERSION}
|