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.
- 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 +126 -0
- package/dist/ui/autocomplete.d.ts +25 -0
- package/dist/ui/autocomplete.js +142 -0
- package/dist/ui/banner.d.ts +24 -0
- package/dist/ui/banner.js +207 -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 +402 -0
- package/package.json +31 -4
- 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 +139 -0
- package/src/ui/autocomplete.ts +153 -0
- package/src/ui/banner.ts +240 -0
- package/src/ui/chat.ts +1 -0
- package/src/ui/colors.ts +70 -0
- package/src/ui/repl.ts +473 -0
- package/tests/cli.test.ts +141 -0
- package/tsconfig.json +18 -0
- package/tsconfig.test.json +7 -0
- package/README.md +0 -3
package/src/ui/banner.ts
ADDED
|
@@ -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';
|
package/src/ui/colors.ts
ADDED
|
@@ -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
|
+
}
|