helix-assistant-cli 0.0.0-stage → 0.2.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/bin/helix.js +3 -0
- package/dist/client/agent-client.d.ts +60 -0
- package/dist/client/agent-client.js +322 -0
- package/dist/client/agent-launcher.d.ts +30 -0
- package/dist/client/agent-launcher.js +339 -0
- package/dist/client/types.d.ts +88 -0
- package/dist/client/types.js +2 -0
- package/dist/commands/apikey.d.ts +3 -0
- package/dist/commands/apikey.js +123 -0
- package/dist/commands/browser.d.ts +2 -0
- package/dist/commands/browser.js +35 -0
- package/dist/commands/mcp.d.ts +3 -0
- package/dist/commands/mcp.js +102 -0
- package/dist/commands/mic.d.ts +2 -0
- package/dist/commands/mic.js +57 -0
- package/dist/commands/skills.d.ts +3 -0
- package/dist/commands/skills.js +105 -0
- package/dist/config/env.d.ts +6 -0
- package/dist/config/env.js +71 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +193 -0
- package/dist/ui/autocomplete.d.ts +25 -0
- package/dist/ui/autocomplete.js +142 -0
- package/dist/ui/banner.d.ts +15 -0
- package/dist/ui/banner.js +84 -0
- package/dist/ui/chat.d.ts +1 -0
- package/dist/ui/chat.js +5 -0
- package/dist/ui/colors.d.ts +34 -0
- package/dist/ui/colors.js +69 -0
- package/dist/ui/repl.d.ts +31 -0
- package/dist/ui/repl.js +448 -0
- package/package.json +30 -3
- package/src/client/agent-client.ts +387 -0
- package/src/client/agent-launcher.ts +374 -0
- package/src/client/types.ts +95 -0
- package/src/commands/apikey.ts +129 -0
- package/src/commands/browser.ts +39 -0
- package/src/commands/mcp.ts +105 -0
- package/src/commands/mic.ts +57 -0
- package/src/commands/skills.ts +109 -0
- package/src/config/env.ts +63 -0
- package/src/index.ts +208 -0
- package/src/ui/autocomplete.ts +153 -0
- package/src/ui/banner.ts +85 -0
- package/src/ui/chat.ts +1 -0
- package/src/ui/colors.ts +70 -0
- package/src/ui/repl.ts +504 -0
- package/tests/cli.test.ts +187 -0
- package/tsconfig.json +19 -0
- package/tsconfig.test.json +7 -0
- package/README.md +0 -3
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.handleListMcp = handleListMcp;
|
|
7
|
+
exports.handleCreateMcp = handleCreateMcp;
|
|
8
|
+
const prompts_1 = __importDefault(require("prompts"));
|
|
9
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
10
|
+
const colors_1 = require("../ui/colors");
|
|
11
|
+
async function handleListMcp(client) {
|
|
12
|
+
console.log(`\n${(0, colors_1.BOLD)('🔌 Servidores y Herramientas MCP:')}\n`);
|
|
13
|
+
try {
|
|
14
|
+
const servers = await client.getMcpServers();
|
|
15
|
+
if (!servers || servers.length === 0) {
|
|
16
|
+
console.log((0, colors_1.GRAY)(' No hay servidores MCP configurados actualmente.'));
|
|
17
|
+
console.log((0, colors_1.DIM)(' Usa /create-mcp para configurar uno nuevo.\n'));
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
for (const server of servers) {
|
|
21
|
+
let statusBadge = chalk_1.default.gray('idle');
|
|
22
|
+
if (server.status === 'connected') {
|
|
23
|
+
statusBadge = chalk_1.default.green('● Conectado');
|
|
24
|
+
}
|
|
25
|
+
else if (server.status === 'connecting') {
|
|
26
|
+
statusBadge = chalk_1.default.yellow('⏳ Conectando');
|
|
27
|
+
}
|
|
28
|
+
else if (server.status === 'error') {
|
|
29
|
+
statusBadge = chalk_1.default.red('✖ Error');
|
|
30
|
+
}
|
|
31
|
+
console.log(` ${(0, colors_1.CYAN_BOLD)('◈')} ${(0, colors_1.formatMcp)(server.name)} [${statusBadge}]`);
|
|
32
|
+
console.log(` ${(0, colors_1.GRAY)('Comando:')} ${chalk_1.default.white(server.command)}`);
|
|
33
|
+
if (server.error) {
|
|
34
|
+
console.log(` ${(0, colors_1.RED)('Error:')} ${server.error}`);
|
|
35
|
+
}
|
|
36
|
+
if (server.tools && server.tools.length > 0) {
|
|
37
|
+
console.log(` ${(0, colors_1.DIM)('Herramientas provistas:')}`);
|
|
38
|
+
for (const tool of server.tools) {
|
|
39
|
+
console.log(` ${(0, colors_1.DIM)('•')} ${(0, colors_1.formatMcp)(tool.name)}${tool.description ? (0, colors_1.DIM)(` - ${tool.description}`) : ''}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
console.log('');
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
console.log((0, colors_1.RED)(` Error al obtener servidores MCP: ${err.message}\n`));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
async function handleCreateMcp(client) {
|
|
50
|
+
console.log(`\n${(0, colors_1.BOLD)('⚙️ Registrar Nuevo Servidor MCP')}\n`);
|
|
51
|
+
const answers = await (0, prompts_1.default)([
|
|
52
|
+
{
|
|
53
|
+
type: 'text',
|
|
54
|
+
name: 'name',
|
|
55
|
+
message: 'Nombre identificador del servidor MCP (ej. filesystem, github, fetch):',
|
|
56
|
+
validate: (v) => /^[a-zA-Z0-9_-]+$/.test(v) ? true : 'Usa solo letras, números, guiones y guiones bajos',
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
type: 'text',
|
|
60
|
+
name: 'command',
|
|
61
|
+
message: 'Comando ejecutable o npx (ej. npx -y @modelcontextprotocol/server-filesystem C:\\work):',
|
|
62
|
+
validate: (v) => (v && v.trim() ? true : 'El comando es requerido'),
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
type: 'text',
|
|
66
|
+
name: 'env',
|
|
67
|
+
message: 'Variables de entorno opcionales (formato KEY=VALUE,KEY2=VALUE2):',
|
|
68
|
+
initial: '',
|
|
69
|
+
},
|
|
70
|
+
]);
|
|
71
|
+
if (!answers.name || !answers.command) {
|
|
72
|
+
console.log((0, colors_1.YELLOW)('\nOperación cancelada.\n'));
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const envObj = {};
|
|
76
|
+
if (answers.env && typeof answers.env === 'string') {
|
|
77
|
+
const pairs = answers.env.split(',');
|
|
78
|
+
for (const pair of pairs) {
|
|
79
|
+
const [k, v] = pair.split('=');
|
|
80
|
+
if (k && v)
|
|
81
|
+
envObj[k.trim()] = v.trim();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
const currentSettings = await client.getSettings();
|
|
86
|
+
const existingServers = currentSettings.mcp_servers || [];
|
|
87
|
+
// Filter out existing with same name
|
|
88
|
+
const updatedServers = existingServers.filter((s) => s.name !== answers.name);
|
|
89
|
+
updatedServers.push({
|
|
90
|
+
name: answers.name.trim(),
|
|
91
|
+
command: answers.command.trim(),
|
|
92
|
+
env: Object.keys(envObj).length > 0 ? envObj : undefined,
|
|
93
|
+
});
|
|
94
|
+
console.log((0, colors_1.GRAY)(`\nGuardando servidor MCP ${(0, colors_1.formatMcp)(answers.name)}...`));
|
|
95
|
+
await client.saveSettings({ mcp_servers: updatedServers });
|
|
96
|
+
console.log((0, colors_1.GREEN)(`✔ ¡Servidor MCP ${(0, colors_1.formatMcp)(answers.name)} registrado con éxito!`));
|
|
97
|
+
console.log((0, colors_1.DIM)(` HELIX sincronizará las herramientas automáticamente.\n`));
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
console.log((0, colors_1.RED)(`✖ Error al registrar servidor MCP: ${err.message}\n`));
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.handleMicrophoneCommand = handleMicrophoneCommand;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const colors_1 = require("../ui/colors");
|
|
9
|
+
async function handleMicrophoneCommand(client, action, currentVoiceState = true) {
|
|
10
|
+
const normalizedAction = action?.toLowerCase().trim();
|
|
11
|
+
if (normalizedAction === 'enable' || normalizedAction === 'on' || normalizedAction === 'true') {
|
|
12
|
+
try {
|
|
13
|
+
console.log((0, colors_1.GRAY)('\nActivando micrófono en el agente HELIX...'));
|
|
14
|
+
await client.saveSettings({ voice_enabled: true });
|
|
15
|
+
console.log(`${(0, colors_1.GREEN)('🟢 Micrófono:')} ${chalk_1.default.greenBright.bold('ACTIVO')} ${(0, colors_1.DIM)('(Escuchando wake word: "hey helix").')}\n`);
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
catch (err) {
|
|
19
|
+
console.log((0, colors_1.RED)(`✖ Error al activar micrófono: ${err.message}\n`));
|
|
20
|
+
return currentVoiceState;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
if (normalizedAction === 'disable' || normalizedAction === 'off' || normalizedAction === 'false') {
|
|
24
|
+
try {
|
|
25
|
+
console.log((0, colors_1.GRAY)('\nDesactivando micrófono en el agente HELIX...'));
|
|
26
|
+
await client.saveSettings({ voice_enabled: false });
|
|
27
|
+
console.log(`${(0, colors_1.RED)('🔴 Micrófono:')} ${chalk_1.default.redBright.bold('INACTIVO')} ${(0, colors_1.DIM)('(Entrada por voz deshabilitada).')}\n`);
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
console.log((0, colors_1.RED)(`✖ Error al desactivar micrófono: ${err.message}\n`));
|
|
32
|
+
return currentVoiceState;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
// If no action or invalid action, show current status and instructions
|
|
36
|
+
try {
|
|
37
|
+
const settings = await client.getSettings();
|
|
38
|
+
const isEnabled = settings.voice_enabled ?? currentVoiceState;
|
|
39
|
+
const wakeWord = settings.wake_word || 'hey helix';
|
|
40
|
+
console.log(`\n${(0, colors_1.BOLD)('🎤 Estado del Micrófono y Reconocimiento de Voz:')}\n`);
|
|
41
|
+
if (isEnabled) {
|
|
42
|
+
console.log(` Estado: ${(0, colors_1.GREEN)('🟢 ACTIVO')}`);
|
|
43
|
+
console.log(` Wake Word: ${chalk_1.default.cyan.bold(`"${wakeWord}"`)}`);
|
|
44
|
+
console.log(` Detalles: El agente procesará tu voz al decir "${wakeWord}".`);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
console.log(` Estado: ${(0, colors_1.RED)('🔴 INACTIVO')}`);
|
|
48
|
+
console.log(` Detalles: La captura de voz está desactivada.`);
|
|
49
|
+
}
|
|
50
|
+
console.log(`\n ${(0, colors_1.DIM)('Uso: /microphone enable | /microphone disable')}\n`);
|
|
51
|
+
return isEnabled;
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
console.log((0, colors_1.RED)(` Error al consultar configuración de voz: ${err.message}\n`));
|
|
55
|
+
return currentVoiceState;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.handleListSkills = handleListSkills;
|
|
7
|
+
exports.handleCreateSkill = handleCreateSkill;
|
|
8
|
+
const prompts_1 = __importDefault(require("prompts"));
|
|
9
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
10
|
+
const colors_1 = require("../ui/colors");
|
|
11
|
+
async function handleListSkills(client, specificSkill) {
|
|
12
|
+
try {
|
|
13
|
+
const skills = await client.getSkills();
|
|
14
|
+
if (!skills || skills.length === 0) {
|
|
15
|
+
console.log((0, colors_1.GRAY)('\n No hay skills registradas en el agente.\n'));
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
if (specificSkill) {
|
|
19
|
+
const match = skills.find((s) => s.name.toLowerCase() === specificSkill.toLowerCase() || `@${s.name.toLowerCase()}` === specificSkill.toLowerCase());
|
|
20
|
+
if (match) {
|
|
21
|
+
console.log(`\n${(0, colors_1.BOLD)('Detalles de la Skill:')}`);
|
|
22
|
+
console.log(` Nombre: ${(0, colors_1.formatSkill)(match.name)}`);
|
|
23
|
+
console.log(` Descripción: ${chalk_1.default.white(match.description || 'Sin descripción')}`);
|
|
24
|
+
if (match.triggers && match.triggers.length > 0) {
|
|
25
|
+
console.log(` Disparadores: ${(0, colors_1.DIM)(match.triggers.join(', '))}`);
|
|
26
|
+
}
|
|
27
|
+
if (match.instructions) {
|
|
28
|
+
console.log(` Instrucciones:\n${(0, colors_1.GRAY)(' ' + match.instructions.replace(/\n/g, '\n '))}`);
|
|
29
|
+
}
|
|
30
|
+
console.log('');
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
console.log((0, colors_1.YELLOW)(`\n Skill "${specificSkill}" no encontrada. Listando todas las disponibles:\n`));
|
|
34
|
+
}
|
|
35
|
+
console.log(`\n${(0, colors_1.BOLD)('⚡ Skills Registradas en HELIX:')}\n`);
|
|
36
|
+
for (const skill of skills) {
|
|
37
|
+
const nameTag = (0, colors_1.formatSkill)(skill.name);
|
|
38
|
+
const isCustom = skill.custom ? chalk_1.default.hex('#FFA500')(' [custom]') : '';
|
|
39
|
+
console.log(` ${(0, colors_1.ORANGE_BOLD)('✦')} ${nameTag}${isCustom}`);
|
|
40
|
+
if (skill.description) {
|
|
41
|
+
console.log(` ${(0, colors_1.DIM)(skill.description)}`);
|
|
42
|
+
}
|
|
43
|
+
if (skill.triggers && skill.triggers.length > 0) {
|
|
44
|
+
console.log(` ${(0, colors_1.GRAY)('Triggers:')} ${(0, colors_1.DIM)(skill.triggers.slice(0, 4).join(', '))}`);
|
|
45
|
+
}
|
|
46
|
+
console.log('');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
console.log((0, colors_1.RED)(` Error al obtener skills: ${err.message}\n`));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
async function handleCreateSkill(client) {
|
|
54
|
+
console.log(`\n${(0, colors_1.BOLD)('🛠️ Crear Nueva Skill en HELIX')}\n`);
|
|
55
|
+
const answers = await (0, prompts_1.default)([
|
|
56
|
+
{
|
|
57
|
+
type: 'text',
|
|
58
|
+
name: 'name',
|
|
59
|
+
message: 'Nombre de la skill (ej. web-summarizer, code-auditor):',
|
|
60
|
+
validate: (v) => /^[a-zA-Z0-9_-]+$/.test(v)
|
|
61
|
+
? true
|
|
62
|
+
: 'El nombre solo puede contener letras, números, guiones y guiones bajos',
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
type: 'text',
|
|
66
|
+
name: 'description',
|
|
67
|
+
message: 'Descripción corta de la funcionalidad:',
|
|
68
|
+
validate: (v) => (v && v.trim() ? true : 'La descripción es requerida'),
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
type: 'text',
|
|
72
|
+
name: 'triggers',
|
|
73
|
+
message: 'Palabras clave disparadoras (separadas por coma, opcional):',
|
|
74
|
+
initial: '',
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
type: 'text',
|
|
78
|
+
name: 'instructions',
|
|
79
|
+
message: 'Instrucciones o system prompt para la ejecución de la skill:',
|
|
80
|
+
validate: (v) => (v && v.trim() ? true : 'Las instrucciones son requeridas'),
|
|
81
|
+
},
|
|
82
|
+
]);
|
|
83
|
+
if (!answers.name || !answers.description || !answers.instructions) {
|
|
84
|
+
console.log((0, colors_1.YELLOW)('\nOperación cancelada.\n'));
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const triggersList = typeof answers.triggers === 'string'
|
|
88
|
+
? answers.triggers.split(',').map((t) => t.trim()).filter(Boolean)
|
|
89
|
+
: [];
|
|
90
|
+
const skillPayload = {
|
|
91
|
+
name: answers.name.trim(),
|
|
92
|
+
description: answers.description.trim(),
|
|
93
|
+
triggers: triggersList,
|
|
94
|
+
instructions: answers.instructions.trim(),
|
|
95
|
+
};
|
|
96
|
+
try {
|
|
97
|
+
console.log((0, colors_1.GRAY)(`\nGuardando skill ${(0, colors_1.formatSkill)(skillPayload.name)}...`));
|
|
98
|
+
await client.saveSkill(skillPayload);
|
|
99
|
+
console.log((0, colors_1.GREEN)(`✔ ¡Skill ${(0, colors_1.formatSkill)(skillPayload.name)} creada con éxito!`));
|
|
100
|
+
console.log((0, colors_1.DIM)(` Puedes invocarla usando: ${(0, colors_1.formatSkill)(skillPayload.name)} <tu consulta>\n`));
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
console.log((0, colors_1.RED)(`✖ Error al crear la skill: ${err.message}\n`));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare function loadHelixEnv(): void;
|
|
2
|
+
export declare function getWsPort(): number;
|
|
3
|
+
export declare function getWsUrl(override?: string): string;
|
|
4
|
+
export declare function getTokenFilePath(): string;
|
|
5
|
+
export declare function getWsToken(override?: string): string | undefined;
|
|
6
|
+
export declare function getSettingsFilePath(): string;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.loadHelixEnv = loadHelixEnv;
|
|
7
|
+
exports.getWsPort = getWsPort;
|
|
8
|
+
exports.getWsUrl = getWsUrl;
|
|
9
|
+
exports.getTokenFilePath = getTokenFilePath;
|
|
10
|
+
exports.getWsToken = getWsToken;
|
|
11
|
+
exports.getSettingsFilePath = getSettingsFilePath;
|
|
12
|
+
const path_1 = __importDefault(require("path"));
|
|
13
|
+
const fs_1 = __importDefault(require("fs"));
|
|
14
|
+
const dotenv_1 = __importDefault(require("dotenv"));
|
|
15
|
+
// Discover .env from current directory, parent dirs, or monorepo root
|
|
16
|
+
function loadHelixEnv() {
|
|
17
|
+
const searchDirs = [
|
|
18
|
+
process.cwd(),
|
|
19
|
+
path_1.default.resolve(process.cwd(), '../..'),
|
|
20
|
+
path_1.default.resolve(__dirname, '../../..'),
|
|
21
|
+
path_1.default.resolve(__dirname, '../../../..'),
|
|
22
|
+
];
|
|
23
|
+
for (const dir of searchDirs) {
|
|
24
|
+
const envPath = path_1.default.join(dir, '.env');
|
|
25
|
+
if (fs_1.default.existsSync(envPath)) {
|
|
26
|
+
dotenv_1.default.config({ path: envPath });
|
|
27
|
+
break;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
// Automatically load on import
|
|
32
|
+
loadHelixEnv();
|
|
33
|
+
function getWsPort() {
|
|
34
|
+
if (process.env.AGENT_WS_PORT && !isNaN(Number(process.env.AGENT_WS_PORT))) {
|
|
35
|
+
return Number(process.env.AGENT_WS_PORT);
|
|
36
|
+
}
|
|
37
|
+
return 8765;
|
|
38
|
+
}
|
|
39
|
+
function getWsUrl(override) {
|
|
40
|
+
if (override)
|
|
41
|
+
return override;
|
|
42
|
+
if (process.env.AGENT_WS_URL)
|
|
43
|
+
return process.env.AGENT_WS_URL;
|
|
44
|
+
return `ws://127.0.0.1:${getWsPort()}`;
|
|
45
|
+
}
|
|
46
|
+
function getTokenFilePath() {
|
|
47
|
+
const appData = process.env.APPDATA || (process.platform === 'darwin' ? path_1.default.join(process.env.HOME || '', 'Library/Application Support') : path_1.default.join(process.env.HOME || '', '.config'));
|
|
48
|
+
return path_1.default.join(appData, 'HELIX', '.ws_token');
|
|
49
|
+
}
|
|
50
|
+
function getWsToken(override) {
|
|
51
|
+
if (override)
|
|
52
|
+
return override;
|
|
53
|
+
if (process.env.HELIX_WS_TOKEN)
|
|
54
|
+
return process.env.HELIX_WS_TOKEN;
|
|
55
|
+
const tokenFile = getTokenFilePath();
|
|
56
|
+
if (fs_1.default.existsSync(tokenFile)) {
|
|
57
|
+
try {
|
|
58
|
+
const saved = fs_1.default.readFileSync(tokenFile, 'utf-8').trim();
|
|
59
|
+
if (saved.length >= 16) {
|
|
60
|
+
process.env.HELIX_WS_TOKEN = saved;
|
|
61
|
+
return saved;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch { }
|
|
65
|
+
}
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
function getSettingsFilePath() {
|
|
69
|
+
const appData = process.env.APPDATA || (process.platform === 'darwin' ? path_1.default.join(process.env.HOME || '', 'Library/Application Support') : path_1.default.join(process.env.HOME || '', '.config'));
|
|
70
|
+
return path_1.default.join(appData, 'HELIX', 'settings.json');
|
|
71
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
// ═══════════════════════════════════════════════════════════════
|
|
4
|
+
// Cross-Platform Terminal & Stream Encoding Setup
|
|
5
|
+
// Compatible with: Windows CMD (CP850/437/1252/65001), PowerShell,
|
|
6
|
+
// Linux terminal (xterm/kitty/gnome), and Bash (MSYS2/Git Bash/WSL).
|
|
7
|
+
// ═══════════════════════════════════════════════════════════════
|
|
8
|
+
if (process.stdin.setEncoding) {
|
|
9
|
+
process.stdin.setEncoding('utf-8');
|
|
10
|
+
}
|
|
11
|
+
if (process.stdout.setDefaultEncoding) {
|
|
12
|
+
process.stdout.setDefaultEncoding('utf-8');
|
|
13
|
+
}
|
|
14
|
+
if (process.stderr.setDefaultEncoding) {
|
|
15
|
+
process.stderr.setDefaultEncoding('utf-8');
|
|
16
|
+
}
|
|
17
|
+
// On Windows, force console code page to 65001 (UTF-8)
|
|
18
|
+
if (process.platform === 'win32') {
|
|
19
|
+
try {
|
|
20
|
+
const { execSync } = require('child_process');
|
|
21
|
+
execSync('chcp 65001 >nul 2>&1', { stdio: 'ignore' });
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
// Silently continue if chcp is unavailable
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const commander_1 = require("commander");
|
|
28
|
+
const repl_1 = require("./ui/repl");
|
|
29
|
+
const env_1 = require("./config/env");
|
|
30
|
+
const agent_client_1 = require("./client/agent-client");
|
|
31
|
+
const apikey_1 = require("./commands/apikey");
|
|
32
|
+
const colors_1 = require("./ui/colors");
|
|
33
|
+
const banner_1 = require("./ui/banner");
|
|
34
|
+
async function readStdin() {
|
|
35
|
+
if (process.stdin.isTTY) {
|
|
36
|
+
return '';
|
|
37
|
+
}
|
|
38
|
+
return new Promise((resolve) => {
|
|
39
|
+
let input = '';
|
|
40
|
+
const onData = (chunk) => {
|
|
41
|
+
input += chunk.toString();
|
|
42
|
+
};
|
|
43
|
+
process.stdin.on('data', onData);
|
|
44
|
+
process.stdin.once('end', () => {
|
|
45
|
+
cleanup();
|
|
46
|
+
if (input.charCodeAt(0) === 0xfeff)
|
|
47
|
+
input = input.slice(1);
|
|
48
|
+
resolve(input.trim());
|
|
49
|
+
});
|
|
50
|
+
process.stdin.once('error', () => {
|
|
51
|
+
cleanup();
|
|
52
|
+
resolve('');
|
|
53
|
+
});
|
|
54
|
+
// If no data arrives and stream is not ended within 300ms, abort to prevent hanging
|
|
55
|
+
const timer = setTimeout(() => {
|
|
56
|
+
cleanup();
|
|
57
|
+
if (input.charCodeAt(0) === 0xfeff)
|
|
58
|
+
input = input.slice(1);
|
|
59
|
+
resolve(input.trim());
|
|
60
|
+
}, 300);
|
|
61
|
+
function cleanup() {
|
|
62
|
+
clearTimeout(timer);
|
|
63
|
+
process.stdin.removeListener('data', onData);
|
|
64
|
+
process.stdin.pause();
|
|
65
|
+
}
|
|
66
|
+
process.stdin.resume();
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
const program = new commander_1.Command();
|
|
70
|
+
program
|
|
71
|
+
.name('helix')
|
|
72
|
+
.description('HELIX - Interactive Terminal CLI for Windows AI Desktop Agent')
|
|
73
|
+
.version('0.1.0')
|
|
74
|
+
.option('-u, --ws-url <url>', 'URL del servidor WebSocket del agente', (0, env_1.getWsUrl)())
|
|
75
|
+
.option('-t, --token <token>', 'Token de autenticación WebSocket de HELIX', (0, env_1.getWsToken)())
|
|
76
|
+
.action(async (options) => {
|
|
77
|
+
// Default action: start interactive chat REPL
|
|
78
|
+
const repl = new repl_1.HelixRepl({
|
|
79
|
+
wsUrl: options.wsUrl || (0, env_1.getWsUrl)(),
|
|
80
|
+
wsToken: options.token || (0, env_1.getWsToken)(),
|
|
81
|
+
});
|
|
82
|
+
await repl.start();
|
|
83
|
+
});
|
|
84
|
+
program
|
|
85
|
+
.command('chat')
|
|
86
|
+
.description('Iniciar sesión de chat interactivo con HELIX')
|
|
87
|
+
.action(async () => {
|
|
88
|
+
const opts = program.opts();
|
|
89
|
+
const repl = new repl_1.HelixRepl({
|
|
90
|
+
wsUrl: opts.wsUrl || (0, env_1.getWsUrl)(),
|
|
91
|
+
wsToken: opts.token || (0, env_1.getWsToken)(),
|
|
92
|
+
});
|
|
93
|
+
await repl.start();
|
|
94
|
+
});
|
|
95
|
+
program
|
|
96
|
+
.command('run [prompt...]')
|
|
97
|
+
.description('Ejecutar un prompt directamente y esperar la respuesta (soporta pipes: cat file | helix run)')
|
|
98
|
+
.action(async (promptParts) => {
|
|
99
|
+
let promptText = (promptParts || []).join(' ').trim();
|
|
100
|
+
if (!promptText || promptText === '-') {
|
|
101
|
+
promptText = await readStdin();
|
|
102
|
+
}
|
|
103
|
+
if (!promptText) {
|
|
104
|
+
console.error((0, colors_1.RED)('Error: Debes proporcionar un prompt como argumento o a través de stdin (ej: echo "hola" | helix run).'));
|
|
105
|
+
process.exit(1);
|
|
106
|
+
}
|
|
107
|
+
const opts = program.opts();
|
|
108
|
+
const client = new agent_client_1.AgentClient({
|
|
109
|
+
wsUrl: opts.wsUrl || (0, env_1.getWsUrl)(),
|
|
110
|
+
wsToken: opts.token || (0, env_1.getWsToken)(),
|
|
111
|
+
autoReconnect: false,
|
|
112
|
+
});
|
|
113
|
+
console.log((0, colors_1.DIM)(`Prompt: ${promptText}\n`));
|
|
114
|
+
console.log((0, colors_1.CYAN)(`Conectando con HELIX en ${client.url}...`));
|
|
115
|
+
try {
|
|
116
|
+
await client.connect();
|
|
117
|
+
}
|
|
118
|
+
catch (err) {
|
|
119
|
+
console.error((0, colors_1.RED)(`No se pudo conectar al agente HELIX: ${err.message}`));
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
const convId = `cli_run_${Date.now()}`;
|
|
123
|
+
client.on('react_step', (step) => {
|
|
124
|
+
process.stdout.write(`\r${(0, colors_1.DIM)(`[ReAct Step ${step?.iteration || 1}...]`)}`);
|
|
125
|
+
});
|
|
126
|
+
client.on('agent_message', (payload) => {
|
|
127
|
+
process.stdout.write('\r\x1b[K');
|
|
128
|
+
console.log(payload?.content || '');
|
|
129
|
+
client.close();
|
|
130
|
+
process.exit(0);
|
|
131
|
+
});
|
|
132
|
+
client.sendUserText(promptText, convId);
|
|
133
|
+
// Timeout safety
|
|
134
|
+
setTimeout(() => {
|
|
135
|
+
console.error((0, colors_1.RED)('\nTiempo de espera agotado sin respuesta del agente.'));
|
|
136
|
+
client.close();
|
|
137
|
+
process.exit(1);
|
|
138
|
+
}, 60000);
|
|
139
|
+
});
|
|
140
|
+
program
|
|
141
|
+
.command('status')
|
|
142
|
+
.description('Consultar el estado del agente y los subsistemas')
|
|
143
|
+
.action(async () => {
|
|
144
|
+
const opts = program.opts();
|
|
145
|
+
const client = new agent_client_1.AgentClient({
|
|
146
|
+
wsUrl: opts.wsUrl || (0, env_1.getWsUrl)(),
|
|
147
|
+
wsToken: opts.token || (0, env_1.getWsToken)(),
|
|
148
|
+
autoReconnect: false,
|
|
149
|
+
});
|
|
150
|
+
console.log((0, banner_1.getAsciiLogo)());
|
|
151
|
+
console.log((0, colors_1.BOLD)('Consultando estado de HELIX...\n'));
|
|
152
|
+
try {
|
|
153
|
+
await client.connect();
|
|
154
|
+
console.log(` ${(0, colors_1.GREEN)('● Conectado')} al agente en ${client.url}`);
|
|
155
|
+
const [settings, skills, mcpServers] = await Promise.all([
|
|
156
|
+
client.getSettings().catch(() => ({})),
|
|
157
|
+
client.getSkills().catch(() => []),
|
|
158
|
+
client.getMcpServers().catch(() => []),
|
|
159
|
+
]);
|
|
160
|
+
const micStatus = settings.voice_enabled !== false ? (0, colors_1.GREEN)('🟢 Activo') : (0, colors_1.RED)('🔴 Inactivo');
|
|
161
|
+
console.log(` Micrófono: ${micStatus}`);
|
|
162
|
+
console.log(` Skills: ${(0, colors_1.ORANGE)(skills.length.toString())} registradas`);
|
|
163
|
+
console.log(` MCP Servers: ${(0, colors_1.CYAN)(mcpServers.length.toString())} configurados`);
|
|
164
|
+
console.log('');
|
|
165
|
+
client.close();
|
|
166
|
+
}
|
|
167
|
+
catch (err) {
|
|
168
|
+
console.log(` ${(0, colors_1.RED)('● Desconectado')} del agente en ${client.url}`);
|
|
169
|
+
console.log((0, colors_1.DIM)(` Detalle: ${err.message}\n`));
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
program
|
|
173
|
+
.command('apikeys')
|
|
174
|
+
.description('Mostrar el estado de las claves API configuradas')
|
|
175
|
+
.action(async () => {
|
|
176
|
+
const opts = program.opts();
|
|
177
|
+
const client = new agent_client_1.AgentClient({
|
|
178
|
+
wsUrl: opts.wsUrl || (0, env_1.getWsUrl)(),
|
|
179
|
+
wsToken: opts.token || (0, env_1.getWsToken)(),
|
|
180
|
+
autoReconnect: false,
|
|
181
|
+
});
|
|
182
|
+
try {
|
|
183
|
+
await client.connect();
|
|
184
|
+
await (0, apikey_1.handleShowApiKey)(client);
|
|
185
|
+
}
|
|
186
|
+
catch (err) {
|
|
187
|
+
console.error((0, colors_1.RED)(`Error de conexión con el agente: ${err.message}`));
|
|
188
|
+
}
|
|
189
|
+
finally {
|
|
190
|
+
client.close();
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
program.parse(process.argv);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { AgentClient } from '../client/agent-client';
|
|
2
|
+
export interface CommandDefinition {
|
|
3
|
+
command: string;
|
|
4
|
+
description: string;
|
|
5
|
+
usage?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare const SLASH_COMMANDS: CommandDefinition[];
|
|
8
|
+
export declare class Autocompleter {
|
|
9
|
+
private client?;
|
|
10
|
+
private skills;
|
|
11
|
+
private mcpTools;
|
|
12
|
+
constructor(client?: AgentClient | undefined);
|
|
13
|
+
refreshSuggestions(): Promise<void>;
|
|
14
|
+
getSkills(): string[];
|
|
15
|
+
getMcpTools(): string[];
|
|
16
|
+
/**
|
|
17
|
+
* Completer function matching readline specification:
|
|
18
|
+
* (line: string) => [string[], string]
|
|
19
|
+
*/
|
|
20
|
+
completer(line: string): [string[], string];
|
|
21
|
+
/**
|
|
22
|
+
* Formats the list of commands for display.
|
|
23
|
+
*/
|
|
24
|
+
renderHelp(): void;
|
|
25
|
+
}
|