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.
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 +193 -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 +15 -0
  25. package/dist/ui/banner.js +84 -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 +448 -0
  32. package/package.json +30 -3
  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 +208 -0
  43. package/src/ui/autocomplete.ts +153 -0
  44. package/src/ui/banner.ts +85 -0
  45. package/src/ui/chat.ts +1 -0
  46. package/src/ui/colors.ts +70 -0
  47. package/src/ui/repl.ts +504 -0
  48. package/tests/cli.test.ts +187 -0
  49. package/tsconfig.json +19 -0
  50. package/tsconfig.test.json +7 -0
  51. package/README.md +0 -3
@@ -0,0 +1,153 @@
1
+ import chalk from 'chalk';
2
+ import type { AgentClient } from '../client/agent-client';
3
+ import { formatSkill, formatMcp, ORANGE, CYAN, MAGENTA, DIM } from './colors';
4
+
5
+ export interface CommandDefinition {
6
+ command: string;
7
+ description: string;
8
+ usage?: string;
9
+ }
10
+
11
+ export const SLASH_COMMANDS: CommandDefinition[] = [
12
+ { command: '/create-skill', description: 'Crear una nueva skill interactiva para HELIX' },
13
+ { command: '/skill', description: 'Listar o inspeccionar skills registradas', usage: '/skill [nombre]' },
14
+ { command: '/mcp', description: 'Listar servidores MCP y sus herramientas' },
15
+ { command: '/create-mcp', description: 'Registrar y configurar un nuevo servidor MCP' },
16
+ { command: '/browser', description: 'Ver estado del navegador web o abrir URLs', usage: '/browser [open <url>]' },
17
+ { command: '/config-apikey', description: 'Configurador interactivo de API Keys (OpenAI, Anthropic, Gemini, etc.)' },
18
+ { command: '/show-apikey', description: 'Ver estado y validez de las API Keys configuradas' },
19
+ { command: '/microphone', description: 'Ver o alternar estado del micrófono', usage: '/microphone [enable|disable]' },
20
+ { command: '/tasks', description: 'Ver tareas activas y recientes del agente' },
21
+ { command: '/models', description: 'Listar modelos disponibles de proveedores configurados' },
22
+ { command: '/status', description: 'Mostrar resumen del estado del sistema' },
23
+ { command: '/clear', description: 'Limpiar pantalla y restaurar el banner de HELIX' },
24
+ { command: '/stop', description: 'Parada de emergencia de todas las tareas activas' },
25
+ { command: '/help', description: 'Mostrar ayuda de comandos y atajos' },
26
+ { command: '/exit', description: 'Salir del CLI' },
27
+ ];
28
+
29
+ export class Autocompleter {
30
+ private skills: string[] = [];
31
+ private mcpTools: string[] = [];
32
+
33
+ constructor(private client?: AgentClient) {}
34
+
35
+ public async refreshSuggestions(): Promise<void> {
36
+ if (!this.client || !this.client.connected) return;
37
+ try {
38
+ const skills = await this.client.getSkills();
39
+ if (skills) {
40
+ this.skills = skills.map((s) => s.name);
41
+ }
42
+ } catch {
43
+ // Ignored if client is offline
44
+ }
45
+
46
+ try {
47
+ const servers = await this.client.getMcpServers();
48
+ if (servers) {
49
+ const tools: string[] = [];
50
+ for (const s of servers) {
51
+ tools.push(s.name);
52
+ if (s.tools) {
53
+ for (const t of s.tools) {
54
+ tools.push(`${s.name}_${t.name}`);
55
+ }
56
+ }
57
+ }
58
+ this.mcpTools = tools;
59
+ }
60
+ } catch {
61
+ // Ignored
62
+ }
63
+ }
64
+
65
+ public getSkills(): string[] {
66
+ return this.skills;
67
+ }
68
+
69
+ public getMcpTools(): string[] {
70
+ return this.mcpTools;
71
+ }
72
+
73
+ /**
74
+ * Completer function matching readline specification:
75
+ * (line: string) => [string[], string]
76
+ */
77
+ public completer(line: string): [string[], string] {
78
+ const trimmed = line.trimStart();
79
+
80
+ // 1. Skill sub-arguments for /skill <name>
81
+ if (trimmed.startsWith('/skill ')) {
82
+ const arg = trimmed.slice('/skill '.length).toLowerCase();
83
+ const hits = this.skills
84
+ .filter((s) => s.toLowerCase().startsWith(arg))
85
+ .map((s) => `/skill ${s}`);
86
+ return [hits.length ? hits : this.skills.map((s) => `/skill ${s}`), line];
87
+ }
88
+
89
+ // 2. Microphone sub-arguments
90
+ if (trimmed.startsWith('/microphone ')) {
91
+ const arg = trimmed.slice('/microphone '.length).toLowerCase();
92
+ const options = ['/microphone enable', '/microphone disable'];
93
+ const hits = options.filter((o) => o.toLowerCase().startsWith(trimmed.toLowerCase()));
94
+ return [hits.length ? hits : options, line];
95
+ }
96
+
97
+ // 3. Browser sub-arguments
98
+ if (trimmed.startsWith('/browser ')) {
99
+ const options = ['/browser open https://'];
100
+ return [options, line];
101
+ }
102
+
103
+ // 4. Slash command autocompletion
104
+ if (trimmed.startsWith('/')) {
105
+ const hits = SLASH_COMMANDS
106
+ .map((c) => c.command)
107
+ .filter((c) => c.toLowerCase().startsWith(trimmed.toLowerCase()));
108
+ return [hits.length ? hits : SLASH_COMMANDS.map((c) => c.command), line];
109
+ }
110
+
111
+ // 5. In-line @skill mention autocompletion
112
+ const lastAtMatch = /(?:^|\s)(@)([a-zA-Z0-9_-]*)$/.exec(line);
113
+ if (lastAtMatch) {
114
+ const prefix = line.slice(0, line.length - lastAtMatch[2].length);
115
+ const query = lastAtMatch[2].toLowerCase();
116
+ const matches = this.skills.filter((s) => s.toLowerCase().startsWith(query));
117
+ const hits = (matches.length ? matches : this.skills).map((s) => `${prefix}${s} `);
118
+ return [hits, line];
119
+ }
120
+
121
+ // 6. In-line #mcp mention autocompletion
122
+ const lastHashMatch = /(?:^|\s)(#)([a-zA-Z0-9_-]*)$/.exec(line);
123
+ if (lastHashMatch) {
124
+ const prefix = line.slice(0, line.length - lastHashMatch[2].length);
125
+ const query = lastHashMatch[2].toLowerCase();
126
+ const matches = this.mcpTools.filter((t) => t.toLowerCase().startsWith(query));
127
+ const hits = (matches.length ? matches : this.mcpTools).map((t) => `${prefix}${t} `);
128
+ return [hits, line];
129
+ }
130
+
131
+ return [[], line];
132
+ }
133
+
134
+ /**
135
+ * Formats the list of commands for display.
136
+ */
137
+ public renderHelp(): void {
138
+ console.log('');
139
+ console.log(` ${chalk.hex('#FF8C00').bold('⬡ Comandos Disponibles')} ${chalk.gray('(/ + [Tab] para autocompletar)')}`);
140
+ console.log(` ${chalk.gray('─'.repeat(50))}`);
141
+ for (const cmd of SLASH_COMMANDS) {
142
+ const usage = cmd.usage ? ` ${chalk.gray(cmd.usage)}` : '';
143
+ console.log(` ${chalk.magenta(cmd.command.padEnd(16))} ${chalk.gray(cmd.description)}${usage}`);
144
+ }
145
+
146
+ console.log('');
147
+ console.log(` ${chalk.hex('#FF8C00').bold('⬡ Prefijos Especiales')}`);
148
+ console.log(` ${chalk.gray('─'.repeat(50))}`);
149
+ console.log(` ${formatSkill('nombre')} ${chalk.gray('Invoca una skill registrada')}`);
150
+ console.log(` ${formatMcp('herramienta')} ${chalk.gray('Invoca herramientas MCP')}`);
151
+ console.log('');
152
+ }
153
+ }
@@ -0,0 +1,85 @@
1
+ import chalk from 'chalk';
2
+ import { ORANGE, CYAN, GREEN, RED, YELLOW, GRAY, BOLD, DIM } from './colors';
3
+
4
+ export interface BannerStatusOptions {
5
+ connected: boolean;
6
+ wsUrl: string;
7
+ voiceEnabled: boolean;
8
+ isCapturingVoice?: boolean;
9
+ currentModel?: string | null;
10
+ activeTasksCount?: number;
11
+ }
12
+
13
+ // Exact bash logo with ANSI RGB codes - DO NOT MODIFY
14
+ export function getAsciiLogo(terminalWidth: number = 80): string {
15
+ const lines = [
16
+ ' \x1b[48;2;255;227;143m\x1b[38;2;255;255;255m▀\x1b[49m\x1b[38;2;255;227;143m█ \x1b[48;2;255;227;143m\x1b[38;2;255;255;255m▀\x1b[49m\x1b[38;2;255;227;143m█\x1b[0m',
17
+ ' \x1b[38;2;255;210;77m██ ██ \x1b[38;2;255;255;255m██████ ██ ██ ██ ██\x1b[0m',
18
+ '\x1b[38;2;255;215;119m██ \x1b[38;2;255;191;31m██ ██ \x1b[38;2;255;215;119m██ \x1b[38;2;246;246;246m██ ██ ██ ██ ██\x1b[0m',
19
+ '\x1b[38;2;255;191;36m██ \x1b[38;2;255;171;10m███\x1b[48;2;255;171;10m\x1b[38;2;255;255;255m▀\x1b[49m\x1b[38;2;255;171;10m███ \x1b[38;2;255;191;36m██ \x1b[38;2;236;236;236m████ ██ ██ ██\x1b[0m',
20
+ '\x1b[38;2;255;160;18m██ \x1b[38;2;255;145;20m███████ \x1b[38;2;255;160;18m██ \x1b[38;2;226;226;226m██ ██ ██ ██ ██\x1b[0m',
21
+ '\x1b[38;2;255;127;26m██ \x1b[38;2;255;122;26m██ ██ \x1b[38;2;255;127;26m██ \x1b[38;2;216;216;216m██████ ██████ ██ ██ ██\x1b[0m',
22
+ '\x1b[38;2;240;98;15m██ \x1b[38;2;255;95;18m██ ██ \x1b[38;2;240;98;15m██\x1b[0m',
23
+ ' \x1b[38;2;220;74;12m██ ██\x1b[0m'
24
+ ];
25
+ const visibleWidth = 62;
26
+ const leftPad = Math.max(0, Math.floor((terminalWidth - visibleWidth) / 2));
27
+ const padding = ' '.repeat(leftPad);
28
+ return lines.map(line => padding + line).join('\n');
29
+ }
30
+
31
+ export function getStatusBar(options: BannerStatusOptions): string {
32
+ const conn = options.connected
33
+ ? `${chalk.green('●')} ${chalk.greenBright.bold('CONECTADO')} ${chalk.gray(options.wsUrl)}`
34
+ : `${chalk.red('●')} ${chalk.redBright.bold('DESCONECTADO')} ${chalk.gray(options.wsUrl)}`;
35
+ let mic: string;
36
+ if (options.isCapturingVoice) {
37
+ mic = chalk.bgRed.white.bold(' 🎙️ CAPTURANDO ');
38
+ } else if (options.voiceEnabled) {
39
+ mic = `${chalk.green('●')} ${chalk.white('Mic')} ${chalk.gray('"hey helix"')}`;
40
+ } else {
41
+ mic = `${chalk.red('●')} ${chalk.gray('Mic Off')}`;
42
+ }
43
+ const model = options.currentModel
44
+ ? `${chalk.hex('#FF9F1C')('⚡')} ${chalk.white(options.currentModel)}`
45
+ : `${chalk.hex('#FF9F1C')('⚡')} ${chalk.white('Local ReAct')}`;
46
+ const tasks = (options.activeTasksCount ?? 0) > 0
47
+ ? ` ${chalk.yellow('⏳')} ${chalk.yellowBright(String(options.activeTasksCount))}`
48
+ : '';
49
+ const sep = chalk.gray(' │ ');
50
+ return ` ${conn}${sep}${mic}${sep}${model}${tasks}`;
51
+ }
52
+
53
+ export function renderHeader(options: BannerStatusOptions, clearScreen: boolean = false): void {
54
+ if (clearScreen) {
55
+ process.stdout.write('\x1bc');
56
+ }
57
+ const width = process.stdout.columns || 80;
58
+ const logo = getAsciiLogo(width);
59
+ const tagline = 'Persistent AI Desktop Agent';
60
+ const tagPad = Math.max(0, Math.floor((width - tagline.length - 8) / 2));
61
+ const subtitle = ' '.repeat(tagPad) + chalk.gray('╶───') + ' ' + chalk.hex('#FF9F1C')(tagline) + ' ' + chalk.gray('───╴');
62
+ const statusBar = getStatusBar(options);
63
+ const thinLine = chalk.gray('─'.repeat(width));
64
+ console.log('');
65
+ console.log(logo);
66
+ console.log('');
67
+ console.log(subtitle);
68
+ console.log('');
69
+ console.log(thinLine);
70
+ console.log(statusBar);
71
+ console.log(thinLine);
72
+ console.log(` ${chalk.hex('#FF8C00')('@skill')} ${chalk.gray('Skills')} ${chalk.gray('│')} ${chalk.cyan('#mcp')} ${chalk.gray('Herramientas')} ${chalk.gray('│')} ${chalk.magenta('/help')} ${chalk.gray('Comandos')}`);
73
+ console.log(thinLine);
74
+ console.log('');
75
+ }
76
+
77
+ export function getVoiceIndicator(isCapturing: boolean, voiceEnabled: boolean): string {
78
+ if (isCapturing) return chalk.bgRed.white.bold(' 🎙️ VOZ DETECTADA [HEY HELIX] 🔊 ');
79
+ if (voiceEnabled) return chalk.green('🟢 [Mic Activo]');
80
+ return chalk.red('🔴 [Mic Inactivo]');
81
+ }
82
+
83
+ export const HELIX_LOGO_ASCII = getAsciiLogo;
84
+ export const HELIX_BIG_ART = getAsciiLogo;
85
+ export const HELIX_HARD_BLOCKS = getAsciiLogo;
package/src/ui/chat.ts ADDED
@@ -0,0 +1 @@
1
+ export { HelixRepl, type ReplOptions } from './repl';
@@ -0,0 +1,70 @@
1
+ import chalk from 'chalk';
2
+
3
+ export const customChalk = new chalk.Instance({ level: 3 });
4
+
5
+ // Brand colors
6
+ export const ORANGE = customChalk.hex('#FF8C00');
7
+ export const ORANGE_BOLD = customChalk.hex('#FF8C00').bold;
8
+ export const ORANGE_BG = customChalk.bgHex('#FF8C00').black.bold;
9
+
10
+ export const CYAN = customChalk.cyan;
11
+ export const CYAN_BOLD = customChalk.cyan.bold;
12
+ export const MAGENTA = customChalk.magenta;
13
+ export const MAGENTA_BOLD = customChalk.magenta.bold;
14
+ export const GREEN = customChalk.green;
15
+ export const GREEN_BOLD = customChalk.green.bold;
16
+ export const RED = customChalk.red;
17
+ export const RED_BOLD = customChalk.red.bold;
18
+ export const YELLOW = customChalk.yellow;
19
+ export const YELLOW_BOLD = customChalk.yellow.bold;
20
+ export const GRAY = customChalk.gray;
21
+ export const BOLD = customChalk.bold;
22
+ export const DIM = customChalk.dim;
23
+
24
+ /**
25
+ * Highlights skill names with strong orange (#FF8C00).
26
+ */
27
+ export function formatSkill(name: string): string {
28
+ const clean = name.startsWith('@') ? name : `@${name}`;
29
+ return ORANGE_BOLD(clean);
30
+ }
31
+
32
+ /**
33
+ * Highlights MCP tool/server names.
34
+ */
35
+ export function formatMcp(name: string): string {
36
+ const clean = name.startsWith('#') ? name : `#${name}`;
37
+ return CYAN_BOLD(clean);
38
+ }
39
+
40
+ /**
41
+ * Highlights slash commands.
42
+ */
43
+ export function formatCommand(cmd: string): string {
44
+ const clean = cmd.startsWith('/') ? cmd : `/${cmd}`;
45
+ return MAGENTA_BOLD(clean);
46
+ }
47
+
48
+ /**
49
+ * Formats user input text highlighting @skills in orange and #mcps in cyan.
50
+ */
51
+ export function highlightMentions(text: string, knownSkills: string[] = []): string {
52
+ let result = text;
53
+
54
+ // Highlight @skill in orange
55
+ result = result.replace(/(^|\s)(@[a-zA-Z0-9_-]+)/g, (_match, prefix, mention) => {
56
+ return `${prefix}${ORANGE_BOLD(mention)}`;
57
+ });
58
+
59
+ // Highlight #mcp in cyan
60
+ result = result.replace(/(^|\s)(#[a-zA-Z0-9_-]+)/g, (_match, prefix, mention) => {
61
+ return `${prefix}${CYAN_BOLD(mention)}`;
62
+ });
63
+
64
+ // Highlight /commands in magenta
65
+ result = result.replace(/(^|\s)(\/[a-zA-Z0-9_-]+)/g, (_match, prefix, mention) => {
66
+ return `${prefix}${MAGENTA_BOLD(mention)}`;
67
+ });
68
+
69
+ return result;
70
+ }