helix-assistant-cli 0.2.1 → 0.2.7
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/dist/index.js +75 -5
- package/dist/skills/user-skill-discovery.d.ts +12 -0
- package/dist/skills/user-skill-discovery.js +52 -0
- package/dist/ui/autocomplete.d.ts +5 -1
- package/dist/ui/autocomplete.js +18 -2
- package/dist/ui/banner.d.ts +3 -12
- package/dist/ui/banner.js +22 -145
- package/dist/ui/markdown.d.ts +5 -0
- package/dist/ui/markdown.js +49 -0
- package/dist/ui/repl.d.ts +2 -0
- package/dist/ui/repl.js +77 -27
- package/package.json +1 -1
- package/src/index.ts +77 -5
- package/src/skills/user-skill-discovery.ts +45 -0
- package/src/ui/autocomplete.ts +21 -2
- package/src/ui/banner.ts +22 -177
- package/src/ui/markdown.ts +51 -0
- package/src/ui/repl.ts +65 -29
- package/tests/cli.test.ts +106 -0
- package/tsconfig.json +2 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
"use strict";
|
|
2
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
|
+
}
|
|
3
27
|
const commander_1 = require("commander");
|
|
4
28
|
const repl_1 = require("./ui/repl");
|
|
5
29
|
const env_1 = require("./config/env");
|
|
@@ -7,18 +31,55 @@ const agent_client_1 = require("./client/agent-client");
|
|
|
7
31
|
const apikey_1 = require("./commands/apikey");
|
|
8
32
|
const colors_1 = require("./ui/colors");
|
|
9
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
|
+
}
|
|
10
69
|
const program = new commander_1.Command();
|
|
11
70
|
program
|
|
12
71
|
.name('helix')
|
|
13
72
|
.description('HELIX - Interactive Terminal CLI for Windows AI Desktop Agent')
|
|
14
|
-
.version('0.
|
|
73
|
+
.version('0.2.7')
|
|
15
74
|
.option('-u, --ws-url <url>', 'URL del servidor WebSocket del agente', (0, env_1.getWsUrl)())
|
|
16
75
|
.option('-t, --token <token>', 'Token de autenticación WebSocket de HELIX', (0, env_1.getWsToken)())
|
|
76
|
+
.option('--plain', 'Desactivar formato enriquecido de Markdown y mostrar texto plano sin asteriscos')
|
|
17
77
|
.action(async (options) => {
|
|
18
78
|
// Default action: start interactive chat REPL
|
|
19
79
|
const repl = new repl_1.HelixRepl({
|
|
20
80
|
wsUrl: options.wsUrl || (0, env_1.getWsUrl)(),
|
|
21
81
|
wsToken: options.token || (0, env_1.getWsToken)(),
|
|
82
|
+
plain: Boolean(options.plain),
|
|
22
83
|
});
|
|
23
84
|
await repl.start();
|
|
24
85
|
});
|
|
@@ -30,19 +91,29 @@ program
|
|
|
30
91
|
const repl = new repl_1.HelixRepl({
|
|
31
92
|
wsUrl: opts.wsUrl || (0, env_1.getWsUrl)(),
|
|
32
93
|
wsToken: opts.token || (0, env_1.getWsToken)(),
|
|
94
|
+
plain: Boolean(opts.plain),
|
|
33
95
|
});
|
|
34
96
|
await repl.start();
|
|
35
97
|
});
|
|
36
98
|
program
|
|
37
|
-
.command('run
|
|
38
|
-
.description('Ejecutar un prompt directamente y esperar la respuesta')
|
|
39
|
-
.action(async (
|
|
99
|
+
.command('run [prompt...]')
|
|
100
|
+
.description('Ejecutar un prompt directamente y esperar la respuesta (soporta pipes: cat file | helix run)')
|
|
101
|
+
.action(async (promptParts) => {
|
|
102
|
+
let promptText = (promptParts || []).join(' ').trim();
|
|
103
|
+
if (!promptText || promptText === '-') {
|
|
104
|
+
promptText = await readStdin();
|
|
105
|
+
}
|
|
106
|
+
if (!promptText) {
|
|
107
|
+
console.error((0, colors_1.RED)('Error: Debes proporcionar un prompt como argumento o a través de stdin (ej: echo "hola" | helix run).'));
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
40
110
|
const opts = program.opts();
|
|
41
111
|
const client = new agent_client_1.AgentClient({
|
|
42
112
|
wsUrl: opts.wsUrl || (0, env_1.getWsUrl)(),
|
|
43
113
|
wsToken: opts.token || (0, env_1.getWsToken)(),
|
|
44
114
|
autoReconnect: false,
|
|
45
115
|
});
|
|
116
|
+
console.log((0, colors_1.DIM)(`Prompt: ${promptText}\n`));
|
|
46
117
|
console.log((0, colors_1.CYAN)(`Conectando con HELIX en ${client.url}...`));
|
|
47
118
|
try {
|
|
48
119
|
await client.connect();
|
|
@@ -51,7 +122,6 @@ program
|
|
|
51
122
|
console.error((0, colors_1.RED)(`No se pudo conectar al agente HELIX: ${err.message}`));
|
|
52
123
|
process.exit(1);
|
|
53
124
|
}
|
|
54
|
-
console.log((0, colors_1.DIM)(`Prompt: ${promptText}\n`));
|
|
55
125
|
const convId = `cli_run_${Date.now()}`;
|
|
56
126
|
client.on('react_step', (step) => {
|
|
57
127
|
process.stdout.write(`\r${(0, colors_1.DIM)(`[ReAct Step ${step?.iteration || 1}...]`)}`);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves the only user-controlled directory used by the CLI skill catalog.
|
|
3
|
+
* HELIX_SKILLS_DIR is explicit; otherwise it follows the agent's settings path.
|
|
4
|
+
*/
|
|
5
|
+
export declare function getUserSkillsDirectory(): string;
|
|
6
|
+
/**
|
|
7
|
+
* Returns names only. The file contents are deliberately never read: skill
|
|
8
|
+
* instructions can contain secrets and are not needed for autocomplete.
|
|
9
|
+
* Discovery is shallow and rejects symlinked entries to avoid following paths
|
|
10
|
+
* outside the user's skills directory.
|
|
11
|
+
*/
|
|
12
|
+
export declare function discoverUserSkillNames(skillsDirectory?: string): string[];
|
|
@@ -0,0 +1,52 @@
|
|
|
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.getUserSkillsDirectory = getUserSkillsDirectory;
|
|
7
|
+
exports.discoverUserSkillNames = discoverUserSkillNames;
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const env_1 = require("../config/env");
|
|
11
|
+
const SKILL_NAME = /^[a-z0-9][a-z0-9_-]{0,63}$/;
|
|
12
|
+
/**
|
|
13
|
+
* Resolves the only user-controlled directory used by the CLI skill catalog.
|
|
14
|
+
* HELIX_SKILLS_DIR is explicit; otherwise it follows the agent's settings path.
|
|
15
|
+
*/
|
|
16
|
+
function getUserSkillsDirectory() {
|
|
17
|
+
const configured = process.env.HELIX_SKILLS_DIR?.trim();
|
|
18
|
+
if (configured)
|
|
19
|
+
return path_1.default.resolve(configured);
|
|
20
|
+
return path_1.default.join(path_1.default.dirname((0, env_1.getSettingsFilePath)()), 'skills');
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Returns names only. The file contents are deliberately never read: skill
|
|
24
|
+
* instructions can contain secrets and are not needed for autocomplete.
|
|
25
|
+
* Discovery is shallow and rejects symlinked entries to avoid following paths
|
|
26
|
+
* outside the user's skills directory.
|
|
27
|
+
*/
|
|
28
|
+
function discoverUserSkillNames(skillsDirectory = getUserSkillsDirectory()) {
|
|
29
|
+
const root = path_1.default.resolve(skillsDirectory);
|
|
30
|
+
let entries;
|
|
31
|
+
try {
|
|
32
|
+
entries = fs_1.default.readdirSync(root, { withFileTypes: true });
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
const names = [];
|
|
38
|
+
for (const entry of entries) {
|
|
39
|
+
if (!entry.isDirectory() || entry.isSymbolicLink() || !SKILL_NAME.test(entry.name))
|
|
40
|
+
continue;
|
|
41
|
+
const skillFile = path_1.default.join(root, entry.name, 'SKILL.md');
|
|
42
|
+
try {
|
|
43
|
+
const file = fs_1.default.lstatSync(skillFile);
|
|
44
|
+
if (file.isFile() && !file.isSymbolicLink())
|
|
45
|
+
names.push(entry.name);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
// Ignore incomplete, inaccessible, or malformed user entries.
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return names.sort((a, b) => a.localeCompare(b, undefined, { sensitivity: 'base' }));
|
|
52
|
+
}
|
|
@@ -8,8 +8,12 @@ export declare const SLASH_COMMANDS: CommandDefinition[];
|
|
|
8
8
|
export declare class Autocompleter {
|
|
9
9
|
private client?;
|
|
10
10
|
private skills;
|
|
11
|
+
private remoteSkills;
|
|
12
|
+
private localSkills;
|
|
11
13
|
private mcpTools;
|
|
12
|
-
|
|
14
|
+
private readonly userSkillsDirectory;
|
|
15
|
+
constructor(client?: AgentClient | undefined, userSkillsDirectory?: string);
|
|
16
|
+
private refreshLocalSkills;
|
|
13
17
|
refreshSuggestions(): Promise<void>;
|
|
14
18
|
getSkills(): string[];
|
|
15
19
|
getMcpTools(): string[];
|
package/dist/ui/autocomplete.js
CHANGED
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.Autocompleter = exports.SLASH_COMMANDS = void 0;
|
|
7
7
|
const chalk_1 = __importDefault(require("chalk"));
|
|
8
8
|
const colors_1 = require("./colors");
|
|
9
|
+
const user_skill_discovery_1 = require("../skills/user-skill-discovery");
|
|
9
10
|
exports.SLASH_COMMANDS = [
|
|
10
11
|
{ command: '/create-skill', description: 'Crear una nueva skill interactiva para HELIX' },
|
|
11
12
|
{ command: '/skill', description: 'Listar o inspeccionar skills registradas', usage: '/skill [nombre]' },
|
|
@@ -26,9 +27,20 @@ exports.SLASH_COMMANDS = [
|
|
|
26
27
|
class Autocompleter {
|
|
27
28
|
client;
|
|
28
29
|
skills = [];
|
|
30
|
+
remoteSkills = [];
|
|
31
|
+
localSkills = [];
|
|
29
32
|
mcpTools = [];
|
|
30
|
-
|
|
33
|
+
userSkillsDirectory;
|
|
34
|
+
constructor(client, userSkillsDirectory = (0, user_skill_discovery_1.getUserSkillsDirectory)()) {
|
|
31
35
|
this.client = client;
|
|
36
|
+
this.userSkillsDirectory = userSkillsDirectory;
|
|
37
|
+
this.refreshLocalSkills();
|
|
38
|
+
}
|
|
39
|
+
refreshLocalSkills() {
|
|
40
|
+
this.localSkills = (0, user_skill_discovery_1.discoverUserSkillNames)(this.userSkillsDirectory);
|
|
41
|
+
const baseSkills = this.client ? this.remoteSkills : this.skills;
|
|
42
|
+
this.skills = [...new Set([...baseSkills, ...this.localSkills])]
|
|
43
|
+
.sort((a, b) => a.localeCompare(b, undefined, { sensitivity: 'base' }));
|
|
32
44
|
}
|
|
33
45
|
async refreshSuggestions() {
|
|
34
46
|
if (!this.client || !this.client.connected)
|
|
@@ -36,7 +48,7 @@ class Autocompleter {
|
|
|
36
48
|
try {
|
|
37
49
|
const skills = await this.client.getSkills();
|
|
38
50
|
if (skills) {
|
|
39
|
-
this.
|
|
51
|
+
this.remoteSkills = skills.map((s) => s.name);
|
|
40
52
|
}
|
|
41
53
|
}
|
|
42
54
|
catch {
|
|
@@ -60,6 +72,7 @@ class Autocompleter {
|
|
|
60
72
|
catch {
|
|
61
73
|
// Ignored
|
|
62
74
|
}
|
|
75
|
+
this.refreshLocalSkills();
|
|
63
76
|
}
|
|
64
77
|
getSkills() {
|
|
65
78
|
return this.skills;
|
|
@@ -72,6 +85,9 @@ class Autocompleter {
|
|
|
72
85
|
* (line: string) => [string[], string]
|
|
73
86
|
*/
|
|
74
87
|
completer(line) {
|
|
88
|
+
// A skill may be created by another HELIX surface while the REPL is open.
|
|
89
|
+
// Keep Tab completion current without exposing the skill file contents.
|
|
90
|
+
this.refreshLocalSkills();
|
|
75
91
|
const trimmed = line.trimStart();
|
|
76
92
|
// 1. Skill sub-arguments for /skill <name>
|
|
77
93
|
if (trimmed.startsWith('/skill ')) {
|
package/dist/ui/banner.d.ts
CHANGED
|
@@ -7,18 +7,9 @@ export interface BannerStatusOptions {
|
|
|
7
7
|
activeTasksCount?: number;
|
|
8
8
|
}
|
|
9
9
|
export declare function getAsciiLogo(terminalWidth?: number): string;
|
|
10
|
-
/**
|
|
11
|
-
* Compact, badge-style status bar.
|
|
12
|
-
*/
|
|
13
10
|
export declare function getStatusBar(options: BannerStatusOptions): string;
|
|
14
|
-
/**
|
|
15
|
-
* Renders the persistent header. Clears screen only if clearScreen is explicitly true.
|
|
16
|
-
*/
|
|
17
11
|
export declare function renderHeader(options: BannerStatusOptions, clearScreen?: boolean): void;
|
|
18
|
-
/**
|
|
19
|
-
* Voice-capture indicator badge (for inline use in prompts).
|
|
20
|
-
*/
|
|
21
12
|
export declare function getVoiceIndicator(isCapturing: boolean, voiceEnabled: boolean): string;
|
|
22
|
-
export declare const HELIX_LOGO_ASCII:
|
|
23
|
-
export declare const HELIX_BIG_ART:
|
|
24
|
-
export declare const HELIX_HARD_BLOCKS:
|
|
13
|
+
export declare const HELIX_LOGO_ASCII: typeof getAsciiLogo;
|
|
14
|
+
export declare const HELIX_BIG_ART: typeof getAsciiLogo;
|
|
15
|
+
export declare const HELIX_HARD_BLOCKS: typeof getAsciiLogo;
|
package/dist/ui/banner.js
CHANGED
|
@@ -9,126 +9,27 @@ exports.getStatusBar = getStatusBar;
|
|
|
9
9
|
exports.renderHeader = renderHeader;
|
|
10
10
|
exports.getVoiceIndicator = getVoiceIndicator;
|
|
11
11
|
const chalk_1 = __importDefault(require("chalk"));
|
|
12
|
-
//
|
|
13
|
-
// HELIX — Pixel-art dot-matrix logo.
|
|
14
|
-
// Each letter is drawn on an 8-row × 5-col grid.
|
|
15
|
-
// "On" pixels → "██" block, "off" pixels → " " (space).
|
|
16
|
-
// Gradient runs top → bottom:
|
|
17
|
-
// rows 0-1 : bright white/cream
|
|
18
|
-
// rows 2-3 : bright yellow
|
|
19
|
-
// rows 4-5 : orange
|
|
20
|
-
// rows 6-7 : deep red
|
|
21
|
-
// Matches the pixel-art HELIX image with yellow→orange→red gradient.
|
|
22
|
-
// ═══════════════════════════════════════════════════════════════
|
|
23
|
-
// ── Letter bitmaps — 1=on, 0=off, 8 rows × 5 cols each ────────
|
|
24
|
-
const H = [
|
|
25
|
-
[1, 0, 0, 0, 1],
|
|
26
|
-
[1, 0, 0, 0, 1],
|
|
27
|
-
[1, 1, 1, 1, 1],
|
|
28
|
-
[1, 1, 1, 1, 1],
|
|
29
|
-
[1, 1, 1, 1, 1],
|
|
30
|
-
[1, 0, 0, 0, 1],
|
|
31
|
-
[1, 0, 0, 0, 1],
|
|
32
|
-
[1, 0, 0, 0, 1],
|
|
33
|
-
];
|
|
34
|
-
const E = [
|
|
35
|
-
[1, 1, 1, 1, 1],
|
|
36
|
-
[1, 0, 0, 0, 0],
|
|
37
|
-
[1, 1, 1, 1, 0],
|
|
38
|
-
[1, 1, 1, 1, 0],
|
|
39
|
-
[1, 1, 1, 1, 0],
|
|
40
|
-
[1, 0, 0, 0, 0],
|
|
41
|
-
[1, 0, 0, 0, 0],
|
|
42
|
-
[1, 1, 1, 1, 1],
|
|
43
|
-
];
|
|
44
|
-
const L = [
|
|
45
|
-
[1, 0, 0, 0, 0],
|
|
46
|
-
[1, 0, 0, 0, 0],
|
|
47
|
-
[1, 0, 0, 0, 0],
|
|
48
|
-
[1, 0, 0, 0, 0],
|
|
49
|
-
[1, 0, 0, 0, 0],
|
|
50
|
-
[1, 0, 0, 0, 0],
|
|
51
|
-
[1, 0, 0, 0, 0],
|
|
52
|
-
[1, 1, 1, 1, 1],
|
|
53
|
-
];
|
|
54
|
-
const I = [
|
|
55
|
-
[1, 1, 1, 1, 1],
|
|
56
|
-
[0, 0, 1, 0, 0],
|
|
57
|
-
[0, 0, 1, 0, 0],
|
|
58
|
-
[0, 0, 1, 0, 0],
|
|
59
|
-
[0, 0, 1, 0, 0],
|
|
60
|
-
[0, 0, 1, 0, 0],
|
|
61
|
-
[0, 0, 1, 0, 0],
|
|
62
|
-
[1, 1, 1, 1, 1],
|
|
63
|
-
];
|
|
64
|
-
const X = [
|
|
65
|
-
[1, 0, 0, 0, 1],
|
|
66
|
-
[1, 1, 0, 1, 1],
|
|
67
|
-
[0, 1, 1, 1, 0],
|
|
68
|
-
[0, 0, 1, 0, 0],
|
|
69
|
-
[0, 0, 1, 0, 0],
|
|
70
|
-
[0, 1, 1, 1, 0],
|
|
71
|
-
[1, 1, 0, 1, 1],
|
|
72
|
-
[1, 0, 0, 0, 1],
|
|
73
|
-
];
|
|
74
|
-
const LETTERS = [H, E, L, I, X];
|
|
75
|
-
const LETTER_GAP = 1; // 1 empty col between letters
|
|
76
|
-
const ROWS = 8;
|
|
77
|
-
const COLS_PER_LETTER = 5;
|
|
78
|
-
// ── Top-to-bottom gradient — cream/white → yellow → orange → red ──
|
|
79
|
-
// Row 0 = top, row 7 = bottom
|
|
80
|
-
const ROW_COLORS = [
|
|
81
|
-
'#FFF9E0', // 0 — near-white cream
|
|
82
|
-
'#FFE84D', // 1 — bright yellow
|
|
83
|
-
'#FFD200', // 2 — gold yellow
|
|
84
|
-
'#FFB300', // 3 — amber
|
|
85
|
-
'#FF8C00', // 4 — orange
|
|
86
|
-
'#FF6600', // 5 — deep orange
|
|
87
|
-
'#E63000', // 6 — red-orange
|
|
88
|
-
'#CC1A00', // 7 — deep red
|
|
89
|
-
];
|
|
90
|
-
const BLOCK_ON = '██'; // filled square block
|
|
91
|
-
const BLOCK_OFF = ' '; // empty (black background)
|
|
92
|
-
// ───────────────────────────────────────────────────────────────
|
|
93
|
-
// Public API
|
|
94
|
-
// ───────────────────────────────────────────────────────────────
|
|
12
|
+
// Exact bash logo with ANSI RGB codes - DO NOT MODIFY
|
|
95
13
|
function getAsciiLogo(terminalWidth = 80) {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
else {
|
|
111
|
-
line += BLOCK_OFF;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
// gap between letters
|
|
115
|
-
if (li < LETTERS.length - 1) {
|
|
116
|
-
line += BLOCK_OFF.repeat(LETTER_GAP);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
lines.push(line);
|
|
120
|
-
}
|
|
121
|
-
return lines.join('\n');
|
|
14
|
+
const lines = [
|
|
15
|
+
' \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',
|
|
16
|
+
' \x1b[38;2;255;210;77m██ ██ \x1b[38;2;255;255;255m██████ ██ ██ ██ ██\x1b[0m',
|
|
17
|
+
'\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',
|
|
18
|
+
'\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',
|
|
19
|
+
'\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',
|
|
20
|
+
'\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',
|
|
21
|
+
'\x1b[38;2;240;98;15m██ \x1b[38;2;255;95;18m██ ██ \x1b[38;2;240;98;15m██\x1b[0m',
|
|
22
|
+
' \x1b[38;2;220;74;12m██ ██\x1b[0m'
|
|
23
|
+
];
|
|
24
|
+
const visibleWidth = 62;
|
|
25
|
+
const leftPad = Math.max(0, Math.floor((terminalWidth - visibleWidth) / 2));
|
|
26
|
+
const padding = ' '.repeat(leftPad);
|
|
27
|
+
return lines.map(line => padding + line).join('\n');
|
|
122
28
|
}
|
|
123
|
-
/**
|
|
124
|
-
* Compact, badge-style status bar.
|
|
125
|
-
*/
|
|
126
29
|
function getStatusBar(options) {
|
|
127
|
-
// ── Connection ──
|
|
128
30
|
const conn = options.connected
|
|
129
31
|
? `${chalk_1.default.green('●')} ${chalk_1.default.greenBright.bold('CONECTADO')} ${chalk_1.default.gray(options.wsUrl)}`
|
|
130
32
|
: `${chalk_1.default.red('●')} ${chalk_1.default.redBright.bold('DESCONECTADO')} ${chalk_1.default.gray(options.wsUrl)}`;
|
|
131
|
-
// ── Microphone ──
|
|
132
33
|
let mic;
|
|
133
34
|
if (options.isCapturingVoice) {
|
|
134
35
|
mic = chalk_1.default.bgRed.white.bold(' 🎙️ CAPTURANDO ');
|
|
@@ -139,42 +40,26 @@ function getStatusBar(options) {
|
|
|
139
40
|
else {
|
|
140
41
|
mic = `${chalk_1.default.red('●')} ${chalk_1.default.gray('Mic Off')}`;
|
|
141
42
|
}
|
|
142
|
-
// ── Model ──
|
|
143
43
|
const model = options.currentModel
|
|
144
44
|
? `${chalk_1.default.hex('#FF9F1C')('⚡')} ${chalk_1.default.white(options.currentModel)}`
|
|
145
45
|
: `${chalk_1.default.hex('#FF9F1C')('⚡')} ${chalk_1.default.white('Local ReAct')}`;
|
|
146
|
-
// ── Active tasks ──
|
|
147
46
|
const tasks = (options.activeTasksCount ?? 0) > 0
|
|
148
47
|
? ` ${chalk_1.default.yellow('⏳')} ${chalk_1.default.yellowBright(String(options.activeTasksCount))}`
|
|
149
48
|
: '';
|
|
150
49
|
const sep = chalk_1.default.gray(' │ ');
|
|
151
50
|
return ` ${conn}${sep}${mic}${sep}${model}${tasks}`;
|
|
152
51
|
}
|
|
153
|
-
/**
|
|
154
|
-
* Renders the persistent header. Clears screen only if clearScreen is explicitly true.
|
|
155
|
-
*/
|
|
156
52
|
function renderHeader(options, clearScreen = false) {
|
|
157
53
|
if (clearScreen) {
|
|
158
|
-
// Clear screen + move cursor to top-left
|
|
159
54
|
process.stdout.write('\x1bc');
|
|
160
55
|
}
|
|
161
56
|
const width = process.stdout.columns || 80;
|
|
162
|
-
// ── Logo ──
|
|
163
57
|
const logo = getAsciiLogo(width);
|
|
164
|
-
// ── Tagline ──
|
|
165
58
|
const tagline = 'Persistent AI Desktop Agent';
|
|
166
59
|
const tagPad = Math.max(0, Math.floor((width - tagline.length - 8) / 2));
|
|
167
|
-
const subtitle = ' '.repeat(tagPad) +
|
|
168
|
-
chalk_1.default.gray('╶───') +
|
|
169
|
-
' ' +
|
|
170
|
-
chalk_1.default.hex('#FF9F1C')(tagline) +
|
|
171
|
-
' ' +
|
|
172
|
-
chalk_1.default.gray('───╴');
|
|
173
|
-
// ── Status bar ──
|
|
60
|
+
const subtitle = ' '.repeat(tagPad) + chalk_1.default.gray('╶───') + ' ' + chalk_1.default.hex('#FF9F1C')(tagline) + ' ' + chalk_1.default.gray('───╴');
|
|
174
61
|
const statusBar = getStatusBar(options);
|
|
175
|
-
// ── Visual lines ──
|
|
176
62
|
const thinLine = chalk_1.default.gray('─'.repeat(width));
|
|
177
|
-
// ── Render ──
|
|
178
63
|
console.log('');
|
|
179
64
|
console.log(logo);
|
|
180
65
|
console.log('');
|
|
@@ -183,25 +68,17 @@ function renderHeader(options, clearScreen = false) {
|
|
|
183
68
|
console.log(thinLine);
|
|
184
69
|
console.log(statusBar);
|
|
185
70
|
console.log(thinLine);
|
|
186
|
-
console.log(` ${chalk_1.default.hex('#FF8C00')('@skill')} ${chalk_1.default.gray('Skills')} ${chalk_1.default.gray('│')} `
|
|
187
|
-
`${chalk_1.default.cyan('#mcp')} ${chalk_1.default.gray('Herramientas')} ${chalk_1.default.gray('│')} ` +
|
|
188
|
-
`${chalk_1.default.magenta('/help')} ${chalk_1.default.gray('Comandos')}`);
|
|
71
|
+
console.log(` ${chalk_1.default.hex('#FF8C00')('@skill')} ${chalk_1.default.gray('Skills')} ${chalk_1.default.gray('│')} ${chalk_1.default.cyan('#mcp')} ${chalk_1.default.gray('Herramientas')} ${chalk_1.default.gray('│')} ${chalk_1.default.magenta('/help')} ${chalk_1.default.gray('Comandos')}`);
|
|
189
72
|
console.log(thinLine);
|
|
190
73
|
console.log('');
|
|
191
74
|
}
|
|
192
|
-
/**
|
|
193
|
-
* Voice-capture indicator badge (for inline use in prompts).
|
|
194
|
-
*/
|
|
195
75
|
function getVoiceIndicator(isCapturing, voiceEnabled) {
|
|
196
|
-
if (isCapturing)
|
|
76
|
+
if (isCapturing)
|
|
197
77
|
return chalk_1.default.bgRed.white.bold(' 🎙️ VOZ DETECTADA [HEY HELIX] 🔊 ');
|
|
198
|
-
|
|
199
|
-
if (voiceEnabled) {
|
|
78
|
+
if (voiceEnabled)
|
|
200
79
|
return chalk_1.default.green('🟢 [Mic Activo]');
|
|
201
|
-
}
|
|
202
80
|
return chalk_1.default.red('🔴 [Mic Inactivo]');
|
|
203
81
|
}
|
|
204
|
-
|
|
205
|
-
exports.
|
|
206
|
-
exports.
|
|
207
|
-
exports.HELIX_HARD_BLOCKS = LETTERS;
|
|
82
|
+
exports.HELIX_LOGO_ASCII = getAsciiLogo;
|
|
83
|
+
exports.HELIX_BIG_ART = getAsciiLogo;
|
|
84
|
+
exports.HELIX_HARD_BLOCKS = getAsciiLogo;
|
|
@@ -0,0 +1,49 @@
|
|
|
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.formatTerminalMarkdown = formatTerminalMarkdown;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
/**
|
|
9
|
+
* Formats Markdown text for optimal display in terminal emulators,
|
|
10
|
+
* resolving visual noise from unparsed asterisks (*, **) and headers.
|
|
11
|
+
*/
|
|
12
|
+
function formatTerminalMarkdown(content, plain = false) {
|
|
13
|
+
if (!content)
|
|
14
|
+
return '';
|
|
15
|
+
const lines = content.split('\n');
|
|
16
|
+
const formattedLines = lines.map((line) => {
|
|
17
|
+
let formatted = line;
|
|
18
|
+
// Headers (#, ##, ###)
|
|
19
|
+
const headerMatch = formatted.match(/^(#{1,6})\s+(.*)$/);
|
|
20
|
+
if (headerMatch) {
|
|
21
|
+
const title = headerMatch[2].trim();
|
|
22
|
+
if (plain) {
|
|
23
|
+
return title.toUpperCase();
|
|
24
|
+
}
|
|
25
|
+
return chalk_1.default.hex('#FF9F1C').bold(title);
|
|
26
|
+
}
|
|
27
|
+
// Bullet lists (* item, - item)
|
|
28
|
+
if (/^\s*[*+-]\s+/.test(formatted)) {
|
|
29
|
+
formatted = formatted.replace(/^(\s*)[*+-]\s+/, (_m, indent) => {
|
|
30
|
+
return plain ? `${indent}- ` : `${indent}${chalk_1.default.hex('#FF8C00')('•')} `;
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
// Inline bold (**text** or __text__)
|
|
34
|
+
formatted = formatted.replace(/(\*\*|__)(.*?)\1/g, (_m, _delim, text) => {
|
|
35
|
+
return plain ? text : chalk_1.default.bold(text);
|
|
36
|
+
});
|
|
37
|
+
// Inline italics (*text* or _text_)
|
|
38
|
+
formatted = formatted.replace(/(?<!\*)\*([^*]+)\*(?!\*)|(?<!_)_([^_]+)_(?!_)/g, (_m, t1, t2) => {
|
|
39
|
+
const text = t1 || t2;
|
|
40
|
+
return plain ? text : chalk_1.default.whiteBright(text);
|
|
41
|
+
});
|
|
42
|
+
// Inline code (`code`)
|
|
43
|
+
formatted = formatted.replace(/`([^`]+)`/g, (_m, code) => {
|
|
44
|
+
return plain ? code : chalk_1.default.cyan(code);
|
|
45
|
+
});
|
|
46
|
+
return formatted;
|
|
47
|
+
});
|
|
48
|
+
return formattedLines.join('\n');
|
|
49
|
+
}
|
package/dist/ui/repl.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export interface ReplOptions {
|
|
2
2
|
wsUrl: string;
|
|
3
3
|
wsToken?: string;
|
|
4
|
+
plain?: boolean;
|
|
4
5
|
}
|
|
5
6
|
export declare class HelixRepl {
|
|
6
7
|
private options;
|
|
@@ -15,6 +16,7 @@ export declare class HelixRepl {
|
|
|
15
16
|
private activeTaskId;
|
|
16
17
|
private activeTasksCount;
|
|
17
18
|
private isProcessingPrompt;
|
|
19
|
+
private plain;
|
|
18
20
|
constructor(options: ReplOptions);
|
|
19
21
|
start(): Promise<void>;
|
|
20
22
|
private _syncSettings;
|