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,240 @@
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
+ // ═══════════════════════════════════════════════════════════════
14
+ // HELIX — Pixel-art dot-matrix logo.
15
+ // Each letter is drawn on an 8-row × 5-col grid.
16
+ // "On" pixels → "██" block, "off" pixels → " " (space).
17
+ // Gradient runs top → bottom:
18
+ // rows 0-1 : bright white/cream
19
+ // rows 2-3 : bright yellow
20
+ // rows 4-5 : orange
21
+ // rows 6-7 : deep red
22
+ // Matches the pixel-art HELIX image with yellow→orange→red gradient.
23
+ // ═══════════════════════════════════════════════════════════════
24
+
25
+ // ── Letter bitmaps — 1=on, 0=off, 8 rows × 5 cols each ────────
26
+
27
+ const H: number[][] = [
28
+ [1,0,0,0,1],
29
+ [1,0,0,0,1],
30
+ [1,1,1,1,1],
31
+ [1,1,1,1,1],
32
+ [1,1,1,1,1],
33
+ [1,0,0,0,1],
34
+ [1,0,0,0,1],
35
+ [1,0,0,0,1],
36
+ ];
37
+
38
+ const E: number[][] = [
39
+ [1,1,1,1,1],
40
+ [1,0,0,0,0],
41
+ [1,1,1,1,0],
42
+ [1,1,1,1,0],
43
+ [1,1,1,1,0],
44
+ [1,0,0,0,0],
45
+ [1,0,0,0,0],
46
+ [1,1,1,1,1],
47
+ ];
48
+
49
+ const L: number[][] = [
50
+ [1,0,0,0,0],
51
+ [1,0,0,0,0],
52
+ [1,0,0,0,0],
53
+ [1,0,0,0,0],
54
+ [1,0,0,0,0],
55
+ [1,0,0,0,0],
56
+ [1,0,0,0,0],
57
+ [1,1,1,1,1],
58
+ ];
59
+
60
+ const I: number[][] = [
61
+ [1,1,1,1,1],
62
+ [0,0,1,0,0],
63
+ [0,0,1,0,0],
64
+ [0,0,1,0,0],
65
+ [0,0,1,0,0],
66
+ [0,0,1,0,0],
67
+ [0,0,1,0,0],
68
+ [1,1,1,1,1],
69
+ ];
70
+
71
+ const X: number[][] = [
72
+ [1,0,0,0,1],
73
+ [1,1,0,1,1],
74
+ [0,1,1,1,0],
75
+ [0,0,1,0,0],
76
+ [0,0,1,0,0],
77
+ [0,1,1,1,0],
78
+ [1,1,0,1,1],
79
+ [1,0,0,0,1],
80
+ ];
81
+
82
+ const LETTERS = [H, E, L, I, X];
83
+ const LETTER_GAP = 1; // 1 empty col between letters
84
+ const ROWS = 8;
85
+ const COLS_PER_LETTER = 5;
86
+
87
+ // ── Top-to-bottom gradient — cream/white → yellow → orange → red ──
88
+ // Row 0 = top, row 7 = bottom
89
+ const ROW_COLORS: string[] = [
90
+ '#FFF9E0', // 0 — near-white cream
91
+ '#FFE84D', // 1 — bright yellow
92
+ '#FFD200', // 2 — gold yellow
93
+ '#FFB300', // 3 — amber
94
+ '#FF8C00', // 4 — orange
95
+ '#FF6600', // 5 — deep orange
96
+ '#E63000', // 6 — red-orange
97
+ '#CC1A00', // 7 — deep red
98
+ ];
99
+
100
+ const BLOCK_ON = '██'; // filled square block
101
+ const BLOCK_OFF = ' '; // empty (black background)
102
+
103
+ // ───────────────────────────────────────────────────────────────
104
+ // Public API
105
+ // ───────────────────────────────────────────────────────────────
106
+
107
+ export function getAsciiLogo(terminalWidth: number = 80): string {
108
+ // total character width: each cell = 2 chars
109
+ const totalCols = LETTERS.length * COLS_PER_LETTER + (LETTERS.length - 1) * LETTER_GAP;
110
+ const totalChars = totalCols * 2;
111
+ const leftPad = Math.max(0, Math.floor((terminalWidth - totalChars) / 2));
112
+ const pad = ' '.repeat(leftPad);
113
+
114
+ const lines: string[] = [];
115
+
116
+ for (let row = 0; row < ROWS; row++) {
117
+ let line = pad;
118
+
119
+ for (let li = 0; li < LETTERS.length; li++) {
120
+ const letter = LETTERS[li];
121
+
122
+ for (let col = 0; col < COLS_PER_LETTER; col++) {
123
+ if (letter[row][col]) {
124
+ line += chalk.hex(ROW_COLORS[row]).bold(BLOCK_ON);
125
+ } else {
126
+ line += BLOCK_OFF;
127
+ }
128
+ }
129
+
130
+ // gap between letters
131
+ if (li < LETTERS.length - 1) {
132
+ line += BLOCK_OFF.repeat(LETTER_GAP);
133
+ }
134
+ }
135
+
136
+ lines.push(line);
137
+ }
138
+
139
+ return lines.join('\n');
140
+ }
141
+
142
+ /**
143
+ * Compact, badge-style status bar.
144
+ */
145
+ export function getStatusBar(options: BannerStatusOptions): string {
146
+ // ── Connection ──
147
+ const conn = options.connected
148
+ ? `${chalk.green('●')} ${chalk.greenBright.bold('CONECTADO')} ${chalk.gray(options.wsUrl)}`
149
+ : `${chalk.red('●')} ${chalk.redBright.bold('DESCONECTADO')} ${chalk.gray(options.wsUrl)}`;
150
+
151
+ // ── Microphone ──
152
+ let mic: string;
153
+ if (options.isCapturingVoice) {
154
+ mic = chalk.bgRed.white.bold(' 🎙️ CAPTURANDO ');
155
+ } else if (options.voiceEnabled) {
156
+ mic = `${chalk.green('●')} ${chalk.white('Mic')} ${chalk.gray('"hey helix"')}`;
157
+ } else {
158
+ mic = `${chalk.red('●')} ${chalk.gray('Mic Off')}`;
159
+ }
160
+
161
+ // ── Model ──
162
+ const model = options.currentModel
163
+ ? `${chalk.hex('#FF9F1C')('⚡')} ${chalk.white(options.currentModel)}`
164
+ : `${chalk.hex('#FF9F1C')('⚡')} ${chalk.white('Local ReAct')}`;
165
+
166
+ // ── Active tasks ──
167
+ const tasks = (options.activeTasksCount ?? 0) > 0
168
+ ? ` ${chalk.yellow('⏳')} ${chalk.yellowBright(String(options.activeTasksCount))}`
169
+ : '';
170
+
171
+ const sep = chalk.gray(' │ ');
172
+ return ` ${conn}${sep}${mic}${sep}${model}${tasks}`;
173
+ }
174
+
175
+ /**
176
+ * Renders the persistent header. Clears screen only if clearScreen is explicitly true.
177
+ */
178
+ export function renderHeader(options: BannerStatusOptions, clearScreen: boolean = false): void {
179
+ if (clearScreen) {
180
+ // Clear screen + move cursor to top-left
181
+ process.stdout.write('\x1bc');
182
+ }
183
+
184
+ const width = process.stdout.columns || 80;
185
+
186
+ // ── Logo ──
187
+ const logo = getAsciiLogo(width);
188
+
189
+ // ── Tagline ──
190
+ const tagline = 'Persistent AI Desktop Agent';
191
+ const tagPad = Math.max(0, Math.floor((width - tagline.length - 8) / 2));
192
+ const subtitle =
193
+ ' '.repeat(tagPad) +
194
+ chalk.gray('╶───') +
195
+ ' ' +
196
+ chalk.hex('#FF9F1C')(tagline) +
197
+ ' ' +
198
+ chalk.gray('───╴');
199
+
200
+ // ── Status bar ──
201
+ const statusBar = getStatusBar(options);
202
+
203
+ // ── Visual lines ──
204
+ const thinLine = chalk.gray('─'.repeat(width));
205
+
206
+ // ── Render ──
207
+ console.log('');
208
+ console.log(logo);
209
+ console.log('');
210
+ console.log(subtitle);
211
+ console.log('');
212
+ console.log(thinLine);
213
+ console.log(statusBar);
214
+ console.log(thinLine);
215
+ console.log(
216
+ ` ${chalk.hex('#FF8C00')('@skill')} ${chalk.gray('Skills')} ${chalk.gray('│')} ` +
217
+ `${chalk.cyan('#mcp')} ${chalk.gray('Herramientas')} ${chalk.gray('│')} ` +
218
+ `${chalk.magenta('/help')} ${chalk.gray('Comandos')}`
219
+ );
220
+ console.log(thinLine);
221
+ console.log('');
222
+ }
223
+
224
+ /**
225
+ * Voice-capture indicator badge (for inline use in prompts).
226
+ */
227
+ export function getVoiceIndicator(isCapturing: boolean, voiceEnabled: boolean): string {
228
+ if (isCapturing) {
229
+ return chalk.bgRed.white.bold(' 🎙️ VOZ DETECTADA [HEY HELIX] 🔊 ');
230
+ }
231
+ if (voiceEnabled) {
232
+ return chalk.green('🟢 [Mic Activo]');
233
+ }
234
+ return chalk.red('🔴 [Mic Inactivo]');
235
+ }
236
+
237
+ // Legacy named exports kept for compatibility with other modules
238
+ export const HELIX_LOGO_ASCII = LETTERS;
239
+ export const HELIX_BIG_ART = LETTERS;
240
+ export const HELIX_HARD_BLOCKS = LETTERS;
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
+ }