helix-assistant-cli 0.0.0-stage → 0.2.1

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.
Files changed (51) hide show
  1. package/bin/helix.js +3 -0
  2. package/dist/client/agent-client.d.ts +60 -0
  3. package/dist/client/agent-client.js +322 -0
  4. package/dist/client/agent-launcher.d.ts +30 -0
  5. package/dist/client/agent-launcher.js +339 -0
  6. package/dist/client/types.d.ts +88 -0
  7. package/dist/client/types.js +2 -0
  8. package/dist/commands/apikey.d.ts +3 -0
  9. package/dist/commands/apikey.js +123 -0
  10. package/dist/commands/browser.d.ts +2 -0
  11. package/dist/commands/browser.js +35 -0
  12. package/dist/commands/mcp.d.ts +3 -0
  13. package/dist/commands/mcp.js +102 -0
  14. package/dist/commands/mic.d.ts +2 -0
  15. package/dist/commands/mic.js +57 -0
  16. package/dist/commands/skills.d.ts +3 -0
  17. package/dist/commands/skills.js +105 -0
  18. package/dist/config/env.d.ts +6 -0
  19. package/dist/config/env.js +71 -0
  20. package/dist/index.d.ts +1 -0
  21. package/dist/index.js +126 -0
  22. package/dist/ui/autocomplete.d.ts +25 -0
  23. package/dist/ui/autocomplete.js +142 -0
  24. package/dist/ui/banner.d.ts +24 -0
  25. package/dist/ui/banner.js +207 -0
  26. package/dist/ui/chat.d.ts +1 -0
  27. package/dist/ui/chat.js +5 -0
  28. package/dist/ui/colors.d.ts +34 -0
  29. package/dist/ui/colors.js +69 -0
  30. package/dist/ui/repl.d.ts +31 -0
  31. package/dist/ui/repl.js +402 -0
  32. package/package.json +31 -4
  33. package/src/client/agent-client.ts +387 -0
  34. package/src/client/agent-launcher.ts +374 -0
  35. package/src/client/types.ts +95 -0
  36. package/src/commands/apikey.ts +129 -0
  37. package/src/commands/browser.ts +39 -0
  38. package/src/commands/mcp.ts +105 -0
  39. package/src/commands/mic.ts +57 -0
  40. package/src/commands/skills.ts +109 -0
  41. package/src/config/env.ts +63 -0
  42. package/src/index.ts +139 -0
  43. package/src/ui/autocomplete.ts +153 -0
  44. package/src/ui/banner.ts +240 -0
  45. package/src/ui/chat.ts +1 -0
  46. package/src/ui/colors.ts +70 -0
  47. package/src/ui/repl.ts +473 -0
  48. package/tests/cli.test.ts +141 -0
  49. package/tsconfig.json +18 -0
  50. package/tsconfig.test.json +7 -0
  51. package/README.md +0 -3
@@ -0,0 +1,402 @@
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.HelixRepl = void 0;
7
+ const readline_1 = __importDefault(require("readline"));
8
+ const chalk_1 = __importDefault(require("chalk"));
9
+ const prompts_1 = __importDefault(require("prompts"));
10
+ const agent_client_1 = require("../client/agent-client");
11
+ const autocomplete_1 = require("./autocomplete");
12
+ const banner_1 = require("./banner");
13
+ const colors_1 = require("./colors");
14
+ const apikey_1 = require("../commands/apikey");
15
+ const skills_1 = require("../commands/skills");
16
+ const mcp_1 = require("../commands/mcp");
17
+ const mic_1 = require("../commands/mic");
18
+ const browser_1 = require("../commands/browser");
19
+ class HelixRepl {
20
+ options;
21
+ rl = null;
22
+ client;
23
+ autocompleter;
24
+ conversationId;
25
+ conversationHistory = [];
26
+ voiceEnabled = true;
27
+ isCapturingVoice = false;
28
+ currentModel = null;
29
+ activeTaskId = null;
30
+ activeTasksCount = 0;
31
+ isProcessingPrompt = false;
32
+ constructor(options) {
33
+ this.options = options;
34
+ this.conversationId = `cli_conv_${Date.now()}_${Math.random().toString(36).substring(2, 7)}`;
35
+ this.client = new agent_client_1.AgentClient({
36
+ wsUrl: options.wsUrl,
37
+ wsToken: options.wsToken,
38
+ });
39
+ this.autocompleter = new autocomplete_1.Autocompleter(this.client);
40
+ }
41
+ async start() {
42
+ this._setupClientEvents();
43
+ try {
44
+ await this.client.connect();
45
+ }
46
+ catch {
47
+ // Connect in background if agent is still starting
48
+ }
49
+ await this._syncSettings();
50
+ await this.autocompleter.refreshSuggestions();
51
+ // Render persistent initial banner
52
+ this.refreshBanner(true);
53
+ this._startReadline();
54
+ }
55
+ async _syncSettings() {
56
+ if (!this.client.connected)
57
+ return;
58
+ try {
59
+ // CLI mode: disable camera/gesture engine and skip per-tool verification
60
+ // (verification adds 2 extra LLM round-trips per tool call, not needed in CLI context)
61
+ await this.client.saveSettings({
62
+ camera_enabled: false,
63
+ tool_verification_enabled: false,
64
+ }).catch(() => { });
65
+ const settings = await this.client.getSettings();
66
+ if (typeof settings.voice_enabled === 'boolean') {
67
+ this.voiceEnabled = settings.voice_enabled;
68
+ }
69
+ if (typeof settings.default_model === 'string') {
70
+ this.currentModel = settings.default_model;
71
+ }
72
+ }
73
+ catch {
74
+ // Fallback defaults
75
+ }
76
+ }
77
+ refreshBanner(clearScreen = false) {
78
+ const status = {
79
+ connected: this.client.connected,
80
+ wsUrl: this.client.url,
81
+ voiceEnabled: this.voiceEnabled,
82
+ isCapturingVoice: this.isCapturingVoice,
83
+ currentModel: this.currentModel,
84
+ activeTasksCount: this.activeTasksCount,
85
+ };
86
+ (0, banner_1.renderHeader)(status, clearScreen);
87
+ }
88
+ waitingLogged = false;
89
+ _setupClientEvents() {
90
+ this.client.on('connected', async () => {
91
+ this.waitingLogged = false;
92
+ await this._syncSettings();
93
+ await this.autocompleter.refreshSuggestions();
94
+ process.stdout.write(`\r\x1b[K${chalk_1.default.green('●')} ${chalk_1.default.greenBright.bold('Conectado al agente HELIX')} \n`);
95
+ this._promptUser();
96
+ });
97
+ this.client.on('disconnected', () => {
98
+ if (this.client.isStarting) {
99
+ return; // Do not interrupt or print disconnect loops while agent is booting
100
+ }
101
+ if (!this.waitingLogged) {
102
+ this.waitingLogged = true;
103
+ console.log(`\n${(0, colors_1.RED)('●')} ${chalk_1.default.yellowBright('Esperando al servicio del agente HELIX en ws://127.0.0.1:8765...')}`);
104
+ console.log(`${(0, colors_1.GRAY)(' (Si no responde tras 3 reintentos, el CLI lo iniciará automáticamente en segundo plano)')}\n`);
105
+ }
106
+ this._promptUser();
107
+ });
108
+ this.client.on('auto_spawning_agent', () => {
109
+ process.stdout.write(`\n${(0, colors_1.CYAN_BOLD)('⚙️')} ${chalk_1.default.hex('#FF9F1C').bold('[AUTO-START]:')} Iniciando el servicio del agente HELIX en segundo plano...\n`);
110
+ process.stdout.write(`${(0, colors_1.GRAY)(' Por favor espere mientras termina de inicializarse (no se realizarán reintentos hasta que esté listo).')}\n`);
111
+ });
112
+ this.client.on('agent_starting_progress', (data) => {
113
+ process.stdout.write(`\r ${chalk_1.default.hex('#FF9F1C')('⏳')} ${chalk_1.default.gray(`Inicializando subsistemas de HELIX en segundo plano... (${data.elapsedSec}s)`)}`);
114
+ });
115
+ this.client.on('agent_ready', () => {
116
+ process.stdout.write(`\r\x1b[K ${chalk_1.default.green('✔')} ${chalk_1.default.greenBright('Servicio de HELIX listo. Conectando...')}\n\n`);
117
+ });
118
+ this.client.on('agent_start_failed', (data) => {
119
+ process.stdout.write(`\r\x1b[K\n ${chalk_1.default.red('✖')} ${chalk_1.default.redBright('No se pudo iniciar el servicio automáticamente:')} ${chalk_1.default.gray(data?.error || 'Tiempo agotado')}\n`);
120
+ this._promptUser();
121
+ });
122
+ // Voice Capture indicator
123
+ this.client.on('voice_capture_started', () => {
124
+ this.isCapturingVoice = true;
125
+ process.stdout.write(`\n${chalk_1.default.bgRed.white.bold(' 🎙️ [VOZ DETECTADA - HEY HELIX]: Capturando audio... ')}\n`);
126
+ });
127
+ this.client.on('voice_capture_stopped', () => {
128
+ this.isCapturingVoice = false;
129
+ process.stdout.write(`\n${chalk_1.default.bgCyan.black.bold(' 🔊 [AUDIO RECIBIDO]: Procesando voz... ')}\n`);
130
+ this._promptUser();
131
+ });
132
+ // ReAct Step Progress
133
+ this.client.on('react_step', (step) => {
134
+ const iter = step?.iteration ?? 1;
135
+ const role = step?.role ? chalk_1.default.gray(` (${step.role})`) : '';
136
+ process.stdout.write(`\r ${chalk_1.default.hex('#FF9F1C')('◆')} ${chalk_1.default.gray(`Paso ${iter}${role} — Razonando...`)}`);
137
+ });
138
+ // Agent assistant message
139
+ this.client.on('agent_message', (payload) => {
140
+ this.isProcessingPrompt = false;
141
+ this.activeTaskId = null;
142
+ if (this.activeTasksCount > 0)
143
+ this.activeTasksCount--;
144
+ const content = payload?.content ?? '';
145
+ const modelBadge = payload?.model
146
+ ? chalk_1.default.gray(` [${payload.model}]`)
147
+ : '';
148
+ // Clear any in-progress step line
149
+ process.stdout.write('\r\x1b[K');
150
+ // Branded header
151
+ console.log('');
152
+ console.log(` ${chalk_1.default.hex('#FF8C00').bold('⬡ HELIX')}${modelBadge}`);
153
+ // Content with subtle left border
154
+ const contentLines = content.trim().split('\n');
155
+ for (const line of contentLines) {
156
+ console.log(` ${chalk_1.default.gray('│')} ${line}`);
157
+ }
158
+ console.log('');
159
+ this.conversationHistory.push({ role: 'assistant', content });
160
+ this._promptUser();
161
+ });
162
+ // Confirmation request from agent
163
+ this.client.on('confirmation_request', async (req) => {
164
+ await this._handleConfirmationDialog(req);
165
+ });
166
+ // Task updates
167
+ this.client.on('task_update', (task) => {
168
+ if (task.status === 'RUNNING') {
169
+ this.activeTaskId = task.task_id || null;
170
+ }
171
+ });
172
+ }
173
+ async _handleConfirmationDialog(req) {
174
+ console.log(`\n${(0, colors_1.YELLOW_BOLD)('⚠️ CONFIRMACIÓN DE ACCIÓN REQUERIDA')}`);
175
+ console.log(` Acción: ${chalk_1.default.white.bold(req.action || req.tool || 'Acción del sistema')}`);
176
+ if (req.description) {
177
+ console.log(` Descripción: ${(0, colors_1.DIM)(req.description)}`);
178
+ }
179
+ if (req.arguments && Object.keys(req.arguments).length > 0) {
180
+ console.log(` Parámetros: ${(0, colors_1.GRAY)(JSON.stringify(req.arguments))}`);
181
+ }
182
+ const answer = await (0, prompts_1.default)({
183
+ type: 'confirm',
184
+ name: 'confirmed',
185
+ message: '¿Permitir al agente ejecutar esta acción?',
186
+ initial: true,
187
+ });
188
+ const reqId = req.request_id || req.id || '';
189
+ if (answer.confirmed) {
190
+ this.client.confirmAction(reqId);
191
+ console.log((0, colors_1.GREEN)(' ✔ Acción confirmada.\n'));
192
+ }
193
+ else {
194
+ this.client.rejectAction(reqId);
195
+ console.log((0, colors_1.RED)(' ✖ Acción rechazada.\n'));
196
+ }
197
+ this._promptUser();
198
+ }
199
+ _startReadline() {
200
+ const rl = readline_1.default.createInterface({
201
+ input: process.stdin,
202
+ output: process.stdout,
203
+ completer: (line) => this.autocompleter.completer(line),
204
+ terminal: true,
205
+ });
206
+ this.rl = rl;
207
+ rl.on('line', async (line) => {
208
+ await this._handleInputLine(line.trim());
209
+ });
210
+ rl.on('close', () => {
211
+ console.log((0, colors_1.DIM)('\nCerrando sesión de HELIX CLI...'));
212
+ this.client.close();
213
+ process.exit(0);
214
+ });
215
+ // Ctrl+C handling: Cancel active task if running, or exit
216
+ rl.on('SIGINT', () => {
217
+ if (this.isProcessingPrompt && this.activeTaskId) {
218
+ console.log((0, colors_1.YELLOW)('\n[!] Cancelando tarea activa...'));
219
+ this.client.cancelTask(this.activeTaskId);
220
+ this.isProcessingPrompt = false;
221
+ this.activeTaskId = null;
222
+ this._promptUser();
223
+ }
224
+ else {
225
+ console.log((0, colors_1.DIM)('\n¡Hasta pronto!'));
226
+ this.client.close();
227
+ process.exit(0);
228
+ }
229
+ });
230
+ this._promptUser();
231
+ }
232
+ _promptUser() {
233
+ if (!this.rl)
234
+ return;
235
+ const promptPrefix = ` ${chalk_1.default.hex('#FF8C00').bold('helix')} ${chalk_1.default.gray('❯')} `;
236
+ this.rl.setPrompt(promptPrefix);
237
+ this.rl.prompt();
238
+ }
239
+ async _handleInputLine(line) {
240
+ if (!line) {
241
+ this._promptUser();
242
+ return;
243
+ }
244
+ if (this.client.isStarting) {
245
+ console.log(`\n ${chalk_1.default.yellow('⚠️')} ${chalk_1.default.yellowBright('El servicio de HELIX se está iniciando en segundo plano. Espera a que termine antes de enviar comandos o reintentar.')}\n`);
246
+ this._promptUser();
247
+ return;
248
+ }
249
+ // --- Slash commands handling ---
250
+ if (line.startsWith('/')) {
251
+ const parts = line.split(/\s+/);
252
+ const cmd = parts[0].toLowerCase();
253
+ const args = line.slice(cmd.length).trim();
254
+ switch (cmd) {
255
+ case '/help':
256
+ this.autocompleter.renderHelp();
257
+ break;
258
+ case '/clear':
259
+ this.refreshBanner(true);
260
+ break;
261
+ case '/config-apikey':
262
+ this.rl?.pause();
263
+ await (0, apikey_1.handleConfigApiKey)(this.client);
264
+ this.rl?.resume();
265
+ break;
266
+ case '/show-apikey':
267
+ await (0, apikey_1.handleShowApiKey)(this.client);
268
+ break;
269
+ case '/skill':
270
+ await (0, skills_1.handleListSkills)(this.client, args || undefined);
271
+ break;
272
+ case '/create-skill':
273
+ this.rl?.pause();
274
+ await (0, skills_1.handleCreateSkill)(this.client);
275
+ await this.autocompleter.refreshSuggestions();
276
+ this.rl?.resume();
277
+ break;
278
+ case '/mcp':
279
+ await (0, mcp_1.handleListMcp)(this.client);
280
+ break;
281
+ case '/create-mcp':
282
+ this.rl?.pause();
283
+ await (0, mcp_1.handleCreateMcp)(this.client);
284
+ await this.autocompleter.refreshSuggestions();
285
+ this.rl?.resume();
286
+ break;
287
+ case '/microphone': {
288
+ const newState = await (0, mic_1.handleMicrophoneCommand)(this.client, args, this.voiceEnabled);
289
+ this.voiceEnabled = newState;
290
+ break;
291
+ }
292
+ case '/browser':
293
+ await (0, browser_1.handleBrowserCommand)(this.client, args);
294
+ break;
295
+ case '/tasks':
296
+ await this._handleListTasks();
297
+ break;
298
+ case '/models':
299
+ await this._handleListModels();
300
+ break;
301
+ case '/status':
302
+ await this._handleStatus();
303
+ break;
304
+ case '/stop':
305
+ console.log((0, colors_1.RED_BOLD)('\n[PARADA DE EMERGENCIA]: Cancelando todas las tareas activas...'));
306
+ this.client.emergencyStop();
307
+ this.isProcessingPrompt = false;
308
+ this.activeTaskId = null;
309
+ console.log((0, colors_1.GREEN)('✔ Todas las tareas han sido detenidas.\n'));
310
+ break;
311
+ case '/exit':
312
+ case '/quit':
313
+ this.client.close();
314
+ process.exit(0);
315
+ break;
316
+ default:
317
+ console.log((0, colors_1.YELLOW)(`Comando desconocido: "${cmd}". Escribe /help para ver la lista de comandos.`));
318
+ break;
319
+ }
320
+ this._promptUser();
321
+ return;
322
+ }
323
+ // --- Direct chat message / @skill / #mcp ---
324
+ if (!this.client.connected) {
325
+ console.log((0, colors_1.YELLOW)('⚠ El agente no está conectado actualmente. Intentando enviar de todos modos...'));
326
+ }
327
+ // Highlight skills in strong orange (#FF8C00) and mcps in cyan
328
+ const knownSkills = this.autocompleter.getSkills();
329
+ const formattedEcho = (0, colors_1.highlightMentions)(line, knownSkills);
330
+ // Record user message in history
331
+ this.conversationHistory.push({ role: 'user', content: line });
332
+ this.isProcessingPrompt = true;
333
+ this.activeTasksCount++;
334
+ process.stdout.write(` ${chalk_1.default.gray('→')} ${formattedEcho}\n`);
335
+ process.stdout.write(` ${chalk_1.default.hex('#FF9F1C')('◆')} ${chalk_1.default.gray('Procesando...')}`);
336
+ const sent = this.client.sendUserText(line, this.conversationId, this.conversationHistory.slice(-10));
337
+ if (!sent) {
338
+ process.stdout.write('\r\x1b[K');
339
+ console.log((0, colors_1.RED)('✖ Error: No se pudo enviar el mensaje (WebSocket desconectado).'));
340
+ this.isProcessingPrompt = false;
341
+ this.activeTasksCount = Math.max(0, this.activeTasksCount - 1);
342
+ this._promptUser();
343
+ }
344
+ }
345
+ async _handleListTasks() {
346
+ try {
347
+ const tasks = await this.client.getTasks();
348
+ console.log(`\n${(0, colors_1.BOLD)('📋 Tareas Registradas en HELIX:')}\n`);
349
+ if (!tasks || tasks.length === 0) {
350
+ console.log((0, colors_1.GRAY)(' No hay tareas recientes registradas.\n'));
351
+ return;
352
+ }
353
+ for (const t of tasks.slice(-10)) {
354
+ let statusBadge = chalk_1.default.gray(t.status);
355
+ if (t.status === 'COMPLETED')
356
+ statusBadge = chalk_1.default.green('✔ Completada');
357
+ if (t.status === 'RUNNING')
358
+ statusBadge = chalk_1.default.yellow('⏳ En ejecución');
359
+ if (t.status === 'FAILED')
360
+ statusBadge = chalk_1.default.red('✖ Fallida');
361
+ if (t.status === 'CANCELLED')
362
+ statusBadge = chalk_1.default.gray('○ Cancelada');
363
+ console.log(` ${statusBadge} ${(0, colors_1.DIM)(t.task_id)}`);
364
+ if (t.prompt)
365
+ console.log(` ${chalk_1.default.white(t.prompt)}`);
366
+ console.log('');
367
+ }
368
+ }
369
+ catch (err) {
370
+ console.log((0, colors_1.RED)(` Error al obtener tareas: ${err.message}\n`));
371
+ }
372
+ }
373
+ async _handleListModels() {
374
+ try {
375
+ const models = await this.client.getModels();
376
+ console.log(`\n${(0, colors_1.BOLD)('🧠 Modelos Disponibles en Proveedores Configurados:')}\n`);
377
+ if (!models || models.length === 0) {
378
+ console.log((0, colors_1.GRAY)(' No hay modelos disponibles. Configura tus API Keys con /config-apikey.\n'));
379
+ return;
380
+ }
381
+ for (const m of models) {
382
+ console.log(` ${(0, colors_1.CYAN)('•')} ${chalk_1.default.bold(m.id)} ${(0, colors_1.DIM)(`[${m.provider}]`)}`);
383
+ }
384
+ console.log('');
385
+ }
386
+ catch (err) {
387
+ console.log((0, colors_1.RED)(` Error al obtener modelos: ${err.message}\n`));
388
+ }
389
+ }
390
+ async _handleStatus() {
391
+ console.log('');
392
+ console.log(` ${chalk_1.default.hex('#FF8C00').bold('⬡ Estado del Sistema')}`);
393
+ console.log(` ${chalk_1.default.gray('─'.repeat(40))}`);
394
+ console.log(` ${chalk_1.default.gray('Agente WS')} ${this.client.connected ? chalk_1.default.green('● Conectado') : chalk_1.default.red('● Desconectado')} ${chalk_1.default.gray(this.client.url)}`);
395
+ console.log(` ${chalk_1.default.gray('Micrófono')} ${this.voiceEnabled ? chalk_1.default.green('● Activo') + chalk_1.default.gray(' (hey helix)') : chalk_1.default.red('● Inactivo')}`);
396
+ console.log(` ${chalk_1.default.gray('Conversación')} ${chalk_1.default.gray(this.conversationId)} ${chalk_1.default.gray(`(${this.conversationHistory.length} msgs)`)}`);
397
+ console.log(` ${chalk_1.default.gray('Skills')} ${chalk_1.default.hex('#FF8C00').bold(this.autocompleter.getSkills().length.toString())} registradas`);
398
+ console.log(` ${chalk_1.default.gray('MCP Tools')} ${chalk_1.default.cyan.bold(this.autocompleter.getMcpTools().length.toString())} disponibles`);
399
+ console.log('');
400
+ }
401
+ }
402
+ exports.HelixRepl = HelixRepl;
package/package.json CHANGED
@@ -1,6 +1,33 @@
1
1
  {
2
2
  "name": "helix-assistant-cli",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.2.1",
4
+ "description": "HELIX - Interactive Terminal CLI for Windows AI Desktop Agent",
5
+ "private": false,
6
+ "publishConfig": {
7
+ "access": "public"
8
+ },
9
+ "bin": {
10
+ "helix": "bin/helix.js"
11
+ },
12
+ "main": "dist/index.js",
13
+ "types": "dist/index.d.ts",
14
+ "scripts": {
15
+ "build": "tsc -p tsconfig.json",
16
+ "dev": "tsc -w -p tsconfig.json",
17
+ "start": "node dist/index.js",
18
+ "test": "node --test --require ts-node/register/transpile-only tests/cli.test.ts"
19
+ },
20
+ "dependencies": {
21
+ "chalk": "^4.1.2",
22
+ "commander": "^12.0.0",
23
+ "dotenv": "^16.4.5",
24
+ "prompts": "^2.4.2",
25
+ "ws": "^8.16.0"
26
+ },
27
+ "devDependencies": {
28
+ "@types/node": "^20.11.0",
29
+ "@types/prompts": "^2.4.9",
30
+ "@types/ws": "^8.5.10",
31
+ "typescript": "^5.3.0"
32
+ }
33
+ }