helix-assistant-cli 0.2.13 → 0.6.0
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/README.md +25 -0
- package/dist/client/agent-launcher.d.ts +0 -4
- package/dist/client/agent-launcher.js +5 -23
- package/dist/commands/browser.js +3 -2
- package/dist/commands/memory.d.ts +6 -0
- package/dist/commands/memory.js +129 -0
- package/dist/commands/model.d.ts +24 -0
- package/dist/commands/model.js +220 -0
- package/dist/commands/subagents.d.ts +15 -0
- package/dist/commands/subagents.js +123 -0
- package/dist/config/mcp-defaults.js +13 -4
- package/dist/index.js +5 -1
- package/dist/ui/autocomplete.js +23 -8
- package/dist/ui/browser-ascii.d.ts +2 -0
- package/dist/ui/browser-ascii.js +67 -0
- package/dist/ui/repl.d.ts +1 -0
- package/dist/ui/repl.js +89 -42
- package/package.json +4 -2
- package/src/client/agent-launcher.ts +6 -25
- package/src/commands/browser.ts +3 -2
- package/src/commands/memory.ts +167 -0
- package/src/commands/model.ts +237 -0
- package/src/commands/subagents.ts +150 -0
- package/src/config/mcp-defaults.ts +13 -4
- package/src/global.d.ts +23 -0
- package/src/index.ts +2 -1
- package/src/ui/autocomplete.ts +26 -9
- package/src/ui/browser-ascii.ts +82 -0
- package/src/ui/repl.ts +101 -43
- package/tests/cli.test.ts +41 -0
- package/tsconfig.json +4 -4
package/README.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# HELIX CLI
|
|
2
|
+
|
|
3
|
+
Interactive terminal client for the local HELIX agent.
|
|
4
|
+
|
|
5
|
+
```powershell
|
|
6
|
+
npx helix-assistant-cli
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
The CLI requires a reachable HELIX agent and a configured model provider for
|
|
10
|
+
AI-backed requests.
|
|
11
|
+
|
|
12
|
+
## Subagents
|
|
13
|
+
|
|
14
|
+
- `/subagents` displays the ASCII profile/status panel.
|
|
15
|
+
- `/subagents chat <profile_id> <message>` sends a direct message to a profile.
|
|
16
|
+
|
|
17
|
+
## Browser snapshots
|
|
18
|
+
|
|
19
|
+
- `/browser open <url>` asks the agent to open a page.
|
|
20
|
+
- `/browser live on` enables the ASCII snapshot viewer; `/browser live off`
|
|
21
|
+
disables it.
|
|
22
|
+
|
|
23
|
+
Frames are JPEG snapshots emitted after successful browser actions, not a
|
|
24
|
+
continuous video stream. Browser-frame routing is limited to the requesting
|
|
25
|
+
conversation.
|
|
@@ -16,10 +16,6 @@ export declare function probePort(port: number, host?: string, timeoutMs?: numbe
|
|
|
16
16
|
* 'closed': Connection refused / port closed
|
|
17
17
|
*/
|
|
18
18
|
export declare function probeWebSocketAuth(port?: number, token?: string, host?: string, timeoutMs?: number): Promise<'ready' | 'unauthorized' | 'closed'>;
|
|
19
|
-
/**
|
|
20
|
-
* Terminates any rogue or orphaned process occupying the specified port.
|
|
21
|
-
*/
|
|
22
|
-
export declare function killProcessOnPort(port?: number): void;
|
|
23
19
|
/**
|
|
24
20
|
* Waits until the agent service has completed startup and successfully accepts WebSocket authentication.
|
|
25
21
|
*/
|
|
@@ -11,7 +11,6 @@ exports.findPythonAgentScript = findPythonAgentScript;
|
|
|
11
11
|
exports.findPythonExecutable = findPythonExecutable;
|
|
12
12
|
exports.probePort = probePort;
|
|
13
13
|
exports.probeWebSocketAuth = probeWebSocketAuth;
|
|
14
|
-
exports.killProcessOnPort = killProcessOnPort;
|
|
15
14
|
exports.waitForAgentReady = waitForAgentReady;
|
|
16
15
|
exports.spawnAgentDaemon = spawnAgentDaemon;
|
|
17
16
|
const child_process_1 = require("child_process");
|
|
@@ -231,24 +230,6 @@ function probeWebSocketAuth(port = 8765, token, host = '127.0.0.1', timeoutMs =
|
|
|
231
230
|
});
|
|
232
231
|
});
|
|
233
232
|
}
|
|
234
|
-
/**
|
|
235
|
-
* Terminates any rogue or orphaned process occupying the specified port.
|
|
236
|
-
*/
|
|
237
|
-
function killProcessOnPort(port = 8765) {
|
|
238
|
-
try {
|
|
239
|
-
if (process.platform === 'win32') {
|
|
240
|
-
(0, child_process_1.execSync)(`powershell -NoProfile -Command "$conn = Get-NetTCPConnection -LocalPort ${port} -State Listen -ErrorAction SilentlyContinue; if ($conn) { Stop-Process -Id $conn.OwningProcess -Force -ErrorAction SilentlyContinue }"`, { stdio: 'ignore', windowsHide: true });
|
|
241
|
-
}
|
|
242
|
-
else {
|
|
243
|
-
(0, child_process_1.execSync)(`fuser -k ${port}/tcp || lsof -ti tcp:${port} | xargs kill -9`, {
|
|
244
|
-
stdio: 'ignore',
|
|
245
|
-
});
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
catch {
|
|
249
|
-
// Ignore errors
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
233
|
/**
|
|
253
234
|
* Waits until the agent service has completed startup and successfully accepts WebSocket authentication.
|
|
254
235
|
*/
|
|
@@ -289,10 +270,11 @@ async function spawnAgentDaemon(port = 8765) {
|
|
|
289
270
|
if (currentStatus === 'ready') {
|
|
290
271
|
return { success: true };
|
|
291
272
|
}
|
|
292
|
-
// 2. If port is occupied by an unauthorized process, clean it up
|
|
293
273
|
if (currentStatus === 'unauthorized') {
|
|
294
|
-
|
|
295
|
-
|
|
274
|
+
return {
|
|
275
|
+
success: false,
|
|
276
|
+
error: `El puerto ${port} ya lo usa un agente HELIX que requiere otro token. Cierra esa instancia o inicia el CLI con --token.`,
|
|
277
|
+
};
|
|
296
278
|
}
|
|
297
279
|
if (isAgentProcessRunning() || isAgentStarting()) {
|
|
298
280
|
return { success: true };
|
|
@@ -310,7 +292,7 @@ async function spawnAgentDaemon(port = 8765) {
|
|
|
310
292
|
try {
|
|
311
293
|
const child = (0, child_process_1.spawn)(pythonExec, ['main.py', '--ws-port', String(port), '--no-camera'], {
|
|
312
294
|
cwd: agentDir,
|
|
313
|
-
detached:
|
|
295
|
+
detached: process.platform !== 'win32',
|
|
314
296
|
stdio: 'ignore', // run completely in background
|
|
315
297
|
windowsHide: true,
|
|
316
298
|
env: {
|
package/dist/commands/browser.js
CHANGED
|
@@ -12,7 +12,7 @@ async function handleBrowserCommand(client, args) {
|
|
|
12
12
|
const targetUrl = parts[1];
|
|
13
13
|
if (subCommand === 'open' && targetUrl) {
|
|
14
14
|
console.log((0, colors_1.CYAN)(`\n Solicitando al agente abrir en navegador: ${targetUrl}...`));
|
|
15
|
-
client.sendUserText(`Por favor abre la siguiente URL en el navegador: ${targetUrl}`, `
|
|
15
|
+
client.sendUserText(`Por favor abre la siguiente URL en el navegador: ${targetUrl}`, `cli_browser_${Date.now()}`);
|
|
16
16
|
console.log((0, colors_1.GREEN)(` ✔ Comando enviado al agente.\n`));
|
|
17
17
|
return;
|
|
18
18
|
}
|
|
@@ -27,7 +27,8 @@ async function handleBrowserCommand(client, args) {
|
|
|
27
27
|
console.log(` Dominios: ${settings.block_untrusted_domains ? (0, colors_1.YELLOW)('Filtrado Estricto') : (0, colors_1.GRAY)('Abierto')}`);
|
|
28
28
|
console.log(`\n ${(0, colors_1.DIM)('Comandos disponibles:')}`);
|
|
29
29
|
console.log(` ${(0, colors_1.CYAN)('/browser')} ${(0, colors_1.DIM)('- Ver estado de navegación')}`);
|
|
30
|
-
console.log(` ${(0, colors_1.CYAN)('/browser open <url>')} ${(0, colors_1.DIM)('- Abrir URL con el agente')}
|
|
30
|
+
console.log(` ${(0, colors_1.CYAN)('/browser open <url>')} ${(0, colors_1.DIM)('- Abrir URL con el agente')}`);
|
|
31
|
+
console.log(` ${(0, colors_1.CYAN)('/browser live [on|off]')} ${(0, colors_1.DIM)('- Alternar la vista ASCII de capturas')}\n`);
|
|
31
32
|
}
|
|
32
33
|
catch (err) {
|
|
33
34
|
console.log((0, colors_1.RED)(` Error al consultar estado del navegador: ${err.message}\n`));
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { AgentClient } from '../client/agent-client';
|
|
2
|
+
export declare function handleMemoryStats(client: AgentClient): Promise<void>;
|
|
3
|
+
export declare function handleMemoryRules(client: AgentClient): Promise<void>;
|
|
4
|
+
export declare function handleMemoryReset(client: AgentClient): Promise<void>;
|
|
5
|
+
/** Top-level dispatcher for /memory <args> */
|
|
6
|
+
export declare function handleMemoryCommand(client: AgentClient, args: string): Promise<void>;
|
|
@@ -0,0 +1,129 @@
|
|
|
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.handleMemoryStats = handleMemoryStats;
|
|
7
|
+
exports.handleMemoryRules = handleMemoryRules;
|
|
8
|
+
exports.handleMemoryReset = handleMemoryReset;
|
|
9
|
+
exports.handleMemoryCommand = handleMemoryCommand;
|
|
10
|
+
/**
|
|
11
|
+
* /memory — CLI command for inspecting the Adaptive Learning Harness.
|
|
12
|
+
*
|
|
13
|
+
* Subcommands:
|
|
14
|
+
* /memory — show quick stats summary
|
|
15
|
+
* /memory stats — detailed episode + rule counts
|
|
16
|
+
* /memory rules — list top learned rules
|
|
17
|
+
* /memory reset — run maintenance / pruning cycle
|
|
18
|
+
*/
|
|
19
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
20
|
+
const colors_1 = require("../ui/colors");
|
|
21
|
+
async function fetchLearningStats(client) {
|
|
22
|
+
try {
|
|
23
|
+
return await client.request('GET_LEARNING_STATS', {});
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
// ── Renderers ─────────────────────────────────────────────────────────────────
|
|
30
|
+
function renderStats(stats) {
|
|
31
|
+
console.log('');
|
|
32
|
+
console.log(` ${chalk_1.default.hex('#FF8C00').bold('⬡ Sistema de Aprendizaje Adaptativo')}`);
|
|
33
|
+
console.log(` ${chalk_1.default.gray('─'.repeat(48))}`);
|
|
34
|
+
const ep = stats.episode_count;
|
|
35
|
+
const ru = stats.rule_count;
|
|
36
|
+
const co = stats.correction_count;
|
|
37
|
+
const sk = stats.skill_count;
|
|
38
|
+
console.log(` ${chalk_1.default.gray('Episodios registrados')} ${chalk_1.default.white.bold(ep.toString())}`);
|
|
39
|
+
console.log(` ${chalk_1.default.gray('Correcciones del usuario')} ${co > 0 ? chalk_1.default.yellow.bold(co.toString()) : chalk_1.default.white(co.toString())}`);
|
|
40
|
+
console.log(` ${chalk_1.default.gray('Reglas aprendidas')} ${chalk_1.default.cyan.bold(ru.toString())}`);
|
|
41
|
+
console.log(` ${chalk_1.default.gray('Skills auto-generadas')} ${chalk_1.default.magenta.bold(sk.toString())}`);
|
|
42
|
+
if (stats.last_episode_at) {
|
|
43
|
+
const d = new Date(stats.last_episode_at);
|
|
44
|
+
console.log(` ${chalk_1.default.gray('Último episodio')} ${(0, colors_1.DIM)(d.toLocaleString())}`);
|
|
45
|
+
}
|
|
46
|
+
const userMdStatus = stats.user_md_exists
|
|
47
|
+
? chalk_1.default.green('✔ USER.md activo')
|
|
48
|
+
: chalk_1.default.gray('○ Sin perfil generado aún');
|
|
49
|
+
console.log(` ${chalk_1.default.gray('Perfil feedforward')} ${userMdStatus}`);
|
|
50
|
+
console.log('');
|
|
51
|
+
}
|
|
52
|
+
function renderRules(stats) {
|
|
53
|
+
if (!stats.top_rules || stats.top_rules.length === 0) {
|
|
54
|
+
console.log((0, colors_1.GRAY)(' No hay reglas aprendidas todavía. Interactúa con HELIX para que aprenda tus preferencias.\n'));
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
console.log('');
|
|
58
|
+
console.log(` ${chalk_1.default.hex('#FF8C00').bold(`✦ Top ${stats.top_rules.length} Reglas Aprendidas`)}`);
|
|
59
|
+
console.log(` ${chalk_1.default.gray('─'.repeat(56))}`);
|
|
60
|
+
for (const rule of stats.top_rules) {
|
|
61
|
+
const conf = Math.round(rule.confidence * 100);
|
|
62
|
+
const bar = '█'.repeat(Math.min(10, Math.floor(conf / 10)));
|
|
63
|
+
const confColor = conf >= 80 ? chalk_1.default.green : conf >= 50 ? chalk_1.default.yellow : chalk_1.default.gray;
|
|
64
|
+
console.log(` ${chalk_1.default.cyan(`[${rule.category}]`).padEnd(24)} ${chalk_1.default.white(rule.rule)}`);
|
|
65
|
+
console.log(` ${(0, colors_1.DIM)('Confianza:')} ${confColor(`${conf}%`)} ${chalk_1.default.gray(bar)} ${(0, colors_1.DIM)(`(${rule.uses} usos)`)}`);
|
|
66
|
+
console.log('');
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// ── Command handlers ──────────────────────────────────────────────────────────
|
|
70
|
+
async function handleMemoryStats(client) {
|
|
71
|
+
const stats = await fetchLearningStats(client);
|
|
72
|
+
if (!stats) {
|
|
73
|
+
console.log((0, colors_1.YELLOW)(' ⚠ El harness de aprendizaje no está disponible (agente desconectado o versión antigua).\n'));
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
renderStats(stats);
|
|
77
|
+
if (stats.top_rules?.length) {
|
|
78
|
+
console.log((0, colors_1.DIM)(' Usa /memory rules para ver las reglas detalladas.'));
|
|
79
|
+
console.log('');
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
async function handleMemoryRules(client) {
|
|
83
|
+
const stats = await fetchLearningStats(client);
|
|
84
|
+
if (!stats) {
|
|
85
|
+
console.log((0, colors_1.YELLOW)(' ⚠ El harness de aprendizaje no está disponible.\n'));
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
renderStats(stats);
|
|
89
|
+
renderRules(stats);
|
|
90
|
+
}
|
|
91
|
+
async function handleMemoryReset(client) {
|
|
92
|
+
try {
|
|
93
|
+
const result = await client.request('RUN_LEARNING_MAINTENANCE', {});
|
|
94
|
+
console.log('');
|
|
95
|
+
console.log((0, colors_1.GREEN)(' ✔ Mantenimiento del harness completado.'));
|
|
96
|
+
console.log(` ${chalk_1.default.gray('Reglas podadas:')} ${chalk_1.default.white.bold(String(result.pruned_rules_count ?? 0))}`);
|
|
97
|
+
const pruned = result.pruned_skills ?? [];
|
|
98
|
+
if (pruned.length) {
|
|
99
|
+
console.log(` ${chalk_1.default.gray('Skills podadas:')} ${chalk_1.default.white(pruned.join(', '))}`);
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
console.log(` ${chalk_1.default.gray('Skills podadas:')} ${(0, colors_1.DIM)('ninguna')}`);
|
|
103
|
+
}
|
|
104
|
+
console.log('');
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
console.log((0, colors_1.RED)(` ✖ Error durante mantenimiento: ${err.message}\n`));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/** Top-level dispatcher for /memory <args> */
|
|
111
|
+
async function handleMemoryCommand(client, args) {
|
|
112
|
+
const sub = args.trim().split(/\s+/)[0]?.toLowerCase() || '';
|
|
113
|
+
switch (sub) {
|
|
114
|
+
case '':
|
|
115
|
+
case 'stats':
|
|
116
|
+
await handleMemoryStats(client);
|
|
117
|
+
break;
|
|
118
|
+
case 'rules':
|
|
119
|
+
await handleMemoryRules(client);
|
|
120
|
+
break;
|
|
121
|
+
case 'reset':
|
|
122
|
+
case 'maintenance':
|
|
123
|
+
case 'prune':
|
|
124
|
+
await handleMemoryReset(client);
|
|
125
|
+
break;
|
|
126
|
+
default:
|
|
127
|
+
console.log((0, colors_1.YELLOW)(` Subcomando desconocido: "${sub}". Opciones: stats, rules, reset\n`));
|
|
128
|
+
}
|
|
129
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { AgentClient } from '../client/agent-client';
|
|
2
|
+
export interface ModelEntry {
|
|
3
|
+
id: string;
|
|
4
|
+
label: string;
|
|
5
|
+
tier: 'free' | 'local' | 'cloud';
|
|
6
|
+
provider: string;
|
|
7
|
+
badge?: string;
|
|
8
|
+
}
|
|
9
|
+
export declare const FREE_MODELS: ModelEntry[];
|
|
10
|
+
export declare const LOCAL_MODELS: ModelEntry[];
|
|
11
|
+
export declare const CLOUD_MODELS: ModelEntry[];
|
|
12
|
+
export declare const ALL_MODELS: ModelEntry[];
|
|
13
|
+
/** /model — show current model */
|
|
14
|
+
export declare function handleModelStatus(client: AgentClient): Promise<void>;
|
|
15
|
+
/** /model list — print the full model catalog */
|
|
16
|
+
export declare function handleModelList(): void;
|
|
17
|
+
/** /model set <id> — switch the active model */
|
|
18
|
+
export declare function handleModelSet(client: AgentClient, modelId: string): Promise<void>;
|
|
19
|
+
/** /model effort <low|medium|high> — set effort/quality level */
|
|
20
|
+
export declare function handleModelEffort(client: AgentClient, level: string): Promise<void>;
|
|
21
|
+
/** /model free-list — list free models fetched from bridge (live) */
|
|
22
|
+
export declare function handleFreeModelList(client: AgentClient): Promise<void>;
|
|
23
|
+
/** Top-level dispatcher for /model <args> */
|
|
24
|
+
export declare function handleModelCommand(client: AgentClient, args: string): Promise<void>;
|
|
@@ -0,0 +1,220 @@
|
|
|
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.ALL_MODELS = exports.CLOUD_MODELS = exports.LOCAL_MODELS = exports.FREE_MODELS = void 0;
|
|
7
|
+
exports.handleModelStatus = handleModelStatus;
|
|
8
|
+
exports.handleModelList = handleModelList;
|
|
9
|
+
exports.handleModelSet = handleModelSet;
|
|
10
|
+
exports.handleModelEffort = handleModelEffort;
|
|
11
|
+
exports.handleFreeModelList = handleFreeModelList;
|
|
12
|
+
exports.handleModelCommand = handleModelCommand;
|
|
13
|
+
/**
|
|
14
|
+
* /model — CLI command for viewing and switching the active AI model.
|
|
15
|
+
*
|
|
16
|
+
* Subcommands:
|
|
17
|
+
* /model — show current model + provider
|
|
18
|
+
* /model list — list all model tiers (Free, Local Ollama, Cloud)
|
|
19
|
+
* /model set <id> — switch to a model by id
|
|
20
|
+
* /model effort <low|medium|high> — set effort/quality level
|
|
21
|
+
*/
|
|
22
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
23
|
+
const colors_1 = require("../ui/colors");
|
|
24
|
+
exports.FREE_MODELS = [
|
|
25
|
+
{
|
|
26
|
+
id: 'meta-llama/llama-3.2-3b-instruct:free',
|
|
27
|
+
label: 'Llama 3.2 3B Instruct',
|
|
28
|
+
tier: 'free',
|
|
29
|
+
provider: 'free',
|
|
30
|
+
badge: '0-cost',
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
id: 'qwen/qwen-2.5-coder-32b-instruct:free',
|
|
34
|
+
label: 'Qwen 2.5 Coder 32B',
|
|
35
|
+
tier: 'free',
|
|
36
|
+
provider: 'free',
|
|
37
|
+
badge: '0-cost',
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
id: 'deepseek/deepseek-r1:free',
|
|
41
|
+
label: 'DeepSeek R1',
|
|
42
|
+
tier: 'free',
|
|
43
|
+
provider: 'free',
|
|
44
|
+
badge: '0-cost · razonamiento',
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
id: 'google/gemini-flash-1.5:free',
|
|
48
|
+
label: 'Gemini Flash 1.5',
|
|
49
|
+
tier: 'free',
|
|
50
|
+
provider: 'free',
|
|
51
|
+
badge: '0-cost · rápido',
|
|
52
|
+
},
|
|
53
|
+
];
|
|
54
|
+
exports.LOCAL_MODELS = [
|
|
55
|
+
{ id: 'ollama/llama3', label: 'Llama 3 (local)', tier: 'local', provider: 'ollama' },
|
|
56
|
+
{ id: 'ollama/mistral', label: 'Mistral 7B (local)', tier: 'local', provider: 'ollama' },
|
|
57
|
+
{ id: 'ollama/deepseek-coder', label: 'DeepSeek Coder (local)', tier: 'local', provider: 'ollama' },
|
|
58
|
+
{ id: 'ollama/phi3', label: 'Phi-3 Mini (local)', tier: 'local', provider: 'ollama' },
|
|
59
|
+
{ id: 'ollama/qwen2.5-coder', label: 'Qwen 2.5 Coder (local)', tier: 'local', provider: 'ollama' },
|
|
60
|
+
];
|
|
61
|
+
exports.CLOUD_MODELS = [
|
|
62
|
+
{ id: 'gpt-4o', label: 'GPT-4o', tier: 'cloud', provider: 'openai' },
|
|
63
|
+
{ id: 'gpt-4o-mini', label: 'GPT-4o Mini', tier: 'cloud', provider: 'openai' },
|
|
64
|
+
{ id: 'claude-3-5-sonnet-20241022', label: 'Claude 3.5 Sonnet', tier: 'cloud', provider: 'anthropic' },
|
|
65
|
+
{ id: 'claude-3-haiku-20240307', label: 'Claude 3 Haiku', tier: 'cloud', provider: 'anthropic' },
|
|
66
|
+
{ id: 'gemini-1.5-pro', label: 'Gemini 1.5 Pro', tier: 'cloud', provider: 'gemini' },
|
|
67
|
+
{ id: 'gemini-1.5-flash', label: 'Gemini 1.5 Flash', tier: 'cloud', provider: 'gemini' },
|
|
68
|
+
];
|
|
69
|
+
exports.ALL_MODELS = [...exports.FREE_MODELS, ...exports.LOCAL_MODELS, ...exports.CLOUD_MODELS];
|
|
70
|
+
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
71
|
+
function providerBadge(entry) {
|
|
72
|
+
if (entry.tier === 'free')
|
|
73
|
+
return chalk_1.default.green.bold('[0-COST]');
|
|
74
|
+
if (entry.tier === 'local')
|
|
75
|
+
return chalk_1.default.cyan.bold('[LOCAL]');
|
|
76
|
+
return chalk_1.default.yellow.bold('[CLOUD]');
|
|
77
|
+
}
|
|
78
|
+
function findModel(id) {
|
|
79
|
+
return exports.ALL_MODELS.find((m) => m.id === id || m.id.startsWith(id));
|
|
80
|
+
}
|
|
81
|
+
// ── Command handlers ──────────────────────────────────────────────────────────
|
|
82
|
+
/** /model — show current model */
|
|
83
|
+
async function handleModelStatus(client) {
|
|
84
|
+
try {
|
|
85
|
+
const settings = await client.getSettings();
|
|
86
|
+
const modelId = settings.default_model || '(none)';
|
|
87
|
+
const provider = settings.preferred_provider || '(none)';
|
|
88
|
+
const entry = findModel(modelId);
|
|
89
|
+
console.log('');
|
|
90
|
+
console.log(` ${chalk_1.default.hex('#FF8C00').bold('⬡ Modelo Activo')}`);
|
|
91
|
+
console.log(` ${chalk_1.default.gray('─'.repeat(44))}`);
|
|
92
|
+
console.log(` ${chalk_1.default.gray('ID')} ${chalk_1.default.white.bold(modelId)}`);
|
|
93
|
+
console.log(` ${chalk_1.default.gray('Proveedor')} ${chalk_1.default.white(provider)}`);
|
|
94
|
+
if (entry) {
|
|
95
|
+
console.log(` ${chalk_1.default.gray('Nombre')} ${entry.label}`);
|
|
96
|
+
console.log(` ${chalk_1.default.gray('Nivel')} ${providerBadge(entry)} ${entry.badge ? (0, colors_1.DIM)(`(${entry.badge})`) : ''}`);
|
|
97
|
+
}
|
|
98
|
+
const speed = settings.speed_preference || 'balanced';
|
|
99
|
+
const quality = settings.quality_preference || 'balanced';
|
|
100
|
+
console.log(` ${chalk_1.default.gray('Velocidad')} ${chalk_1.default.cyan(speed)}`);
|
|
101
|
+
console.log(` ${chalk_1.default.gray('Calidad')} ${chalk_1.default.cyan(quality)}`);
|
|
102
|
+
console.log('');
|
|
103
|
+
console.log((0, colors_1.DIM)(' Usa /model list para ver opciones, /model set <id> para cambiar.'));
|
|
104
|
+
console.log('');
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
console.log((0, colors_1.RED)(` Error al obtener modelo: ${err.message}\n`));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/** /model list — print the full model catalog */
|
|
111
|
+
function handleModelList() {
|
|
112
|
+
const printSection = (title, models) => {
|
|
113
|
+
console.log(`\n ${chalk_1.default.hex('#FF8C00').bold(title)}`);
|
|
114
|
+
console.log(` ${chalk_1.default.gray('─'.repeat(60))}`);
|
|
115
|
+
for (const m of models) {
|
|
116
|
+
const badge = providerBadge(m);
|
|
117
|
+
const extra = m.badge ? (0, colors_1.DIM)(` · ${m.badge}`) : '';
|
|
118
|
+
console.log(` ${badge} ${chalk_1.default.white.bold(m.label.padEnd(32))} ${(0, colors_1.GRAY)(m.id)}${extra}`);
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
console.log('');
|
|
122
|
+
console.log(` ${(0, colors_1.BOLD)('🧠 Catálogo de Modelos HELIX')}`);
|
|
123
|
+
printSection('✦ Nivel 1 — Sin costo (OpenRouter Free)', exports.FREE_MODELS);
|
|
124
|
+
printSection('✦ Nivel 2 — Local Ollama (sin internet)', exports.LOCAL_MODELS);
|
|
125
|
+
printSection('✦ Nivel 3 — Nube con API Key propia', exports.CLOUD_MODELS);
|
|
126
|
+
console.log('');
|
|
127
|
+
console.log((0, colors_1.DIM)(' Usa: /model set <model-id> para activar un modelo'));
|
|
128
|
+
console.log((0, colors_1.DIM)(' Usa: /model effort <low|medium|high> para ajustar esfuerzo'));
|
|
129
|
+
console.log('');
|
|
130
|
+
}
|
|
131
|
+
/** /model set <id> — switch the active model */
|
|
132
|
+
async function handleModelSet(client, modelId) {
|
|
133
|
+
if (!modelId) {
|
|
134
|
+
console.log((0, colors_1.YELLOW)(' Uso: /model set <model-id>\n'));
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const entry = findModel(modelId);
|
|
138
|
+
const provider = entry?.provider || (modelId.startsWith('ollama/') ? 'ollama' : 'openai');
|
|
139
|
+
try {
|
|
140
|
+
await client.saveSettings({
|
|
141
|
+
default_model: entry?.id ?? modelId,
|
|
142
|
+
preferred_provider: provider,
|
|
143
|
+
});
|
|
144
|
+
const badge = entry ? providerBadge(entry) : '';
|
|
145
|
+
console.log('');
|
|
146
|
+
console.log((0, colors_1.GREEN)(' ✔ Modelo actualizado correctamente.'));
|
|
147
|
+
console.log(` ${chalk_1.default.gray('Modelo:')} ${chalk_1.default.white.bold(entry?.id ?? modelId)} ${badge}`);
|
|
148
|
+
console.log(` ${chalk_1.default.gray('Proveedor:')} ${chalk_1.default.white(provider)}`);
|
|
149
|
+
console.log('');
|
|
150
|
+
}
|
|
151
|
+
catch (err) {
|
|
152
|
+
console.log((0, colors_1.RED)(` ✖ Error al cambiar modelo: ${err.message}\n`));
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/** /model effort <low|medium|high> — set effort/quality level */
|
|
156
|
+
async function handleModelEffort(client, level) {
|
|
157
|
+
const valid = ['low', 'medium', 'high'];
|
|
158
|
+
const normalized = level?.toLowerCase();
|
|
159
|
+
if (!normalized || !valid.includes(normalized)) {
|
|
160
|
+
console.log((0, colors_1.YELLOW)(' Uso: /model effort <low|medium|high>\n'));
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
const speedMap = { low: 'fast', medium: 'balanced', high: 'balanced' };
|
|
164
|
+
const qualityMap = { low: 'fast', medium: 'balanced', high: 'best' };
|
|
165
|
+
try {
|
|
166
|
+
await client.saveSettings({
|
|
167
|
+
speed_preference: speedMap[normalized],
|
|
168
|
+
quality_preference: qualityMap[normalized],
|
|
169
|
+
});
|
|
170
|
+
console.log('');
|
|
171
|
+
console.log((0, colors_1.GREEN)(` ✔ Nivel de esfuerzo → ${chalk_1.default.white.bold(normalized.toUpperCase())}`));
|
|
172
|
+
console.log((0, colors_1.DIM)(` Velocidad: ${speedMap[normalized]} | Calidad: ${qualityMap[normalized]}`));
|
|
173
|
+
console.log('');
|
|
174
|
+
}
|
|
175
|
+
catch (err) {
|
|
176
|
+
console.log((0, colors_1.RED)(` ✖ Error al ajustar esfuerzo: ${err.message}\n`));
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
/** /model free-list — list free models fetched from bridge (live) */
|
|
180
|
+
async function handleFreeModelList(client) {
|
|
181
|
+
try {
|
|
182
|
+
const result = await client
|
|
183
|
+
.request('LIST_FREE_MODELS', {});
|
|
184
|
+
if (!result?.models?.length) {
|
|
185
|
+
// Fallback to static catalog
|
|
186
|
+
handleModelList();
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
console.log(`\n ${(0, colors_1.BOLD)('✦ Modelos gratuitos disponibles (live):')} \n`);
|
|
190
|
+
for (const m of result.models) {
|
|
191
|
+
console.log(` ${(0, colors_1.GREEN)('[0-COST]')} ${chalk_1.default.white.bold(m.label)} ${(0, colors_1.GRAY)(m.id)}`);
|
|
192
|
+
}
|
|
193
|
+
console.log('');
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
// Agent not connected or handler not present — fall back to static
|
|
197
|
+
handleModelList();
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
/** Top-level dispatcher for /model <args> */
|
|
201
|
+
async function handleModelCommand(client, args) {
|
|
202
|
+
const parts = args.trim().split(/\s+/);
|
|
203
|
+
const sub = parts[0]?.toLowerCase() || '';
|
|
204
|
+
switch (sub) {
|
|
205
|
+
case '':
|
|
206
|
+
await handleModelStatus(client);
|
|
207
|
+
break;
|
|
208
|
+
case 'list':
|
|
209
|
+
handleModelList();
|
|
210
|
+
break;
|
|
211
|
+
case 'set':
|
|
212
|
+
await handleModelSet(client, parts.slice(1).join(' '));
|
|
213
|
+
break;
|
|
214
|
+
case 'effort':
|
|
215
|
+
await handleModelEffort(client, parts[1] || '');
|
|
216
|
+
break;
|
|
217
|
+
default:
|
|
218
|
+
console.log((0, colors_1.YELLOW)(` Subcomando desconocido: "${sub}". Opciones: list, set, effort\n`));
|
|
219
|
+
}
|
|
220
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { AgentClient } from '../client/agent-client';
|
|
2
|
+
interface SubagentProfile {
|
|
3
|
+
profile_id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
}
|
|
6
|
+
interface SubagentStatus {
|
|
7
|
+
profile_id?: string;
|
|
8
|
+
name?: string;
|
|
9
|
+
status?: string;
|
|
10
|
+
progress?: number;
|
|
11
|
+
current_action?: string;
|
|
12
|
+
}
|
|
13
|
+
export declare function renderSubagentPanel(profiles: SubagentProfile[], statuses: SubagentStatus[]): string;
|
|
14
|
+
export declare function handleSubagentsCommand(client: AgentClient, args?: string): Promise<void>;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.renderSubagentPanel = renderSubagentPanel;
|
|
4
|
+
exports.handleSubagentsCommand = handleSubagentsCommand;
|
|
5
|
+
const colors_1 = require("../ui/colors");
|
|
6
|
+
function boundedText(value, maxLength) {
|
|
7
|
+
if (typeof value !== 'string')
|
|
8
|
+
return '';
|
|
9
|
+
const clean = value.replace(/[\r\n|]/g, ' ').trim();
|
|
10
|
+
return clean.length > maxLength ? `${clean.slice(0, maxLength - 3)}...` : clean;
|
|
11
|
+
}
|
|
12
|
+
function statusLabel(status) {
|
|
13
|
+
const normalized = status?.toLowerCase() || 'idle';
|
|
14
|
+
if (['running', 'active', 'in_progress'].includes(normalized))
|
|
15
|
+
return 'RUNNING';
|
|
16
|
+
if (['failed', 'error'].includes(normalized))
|
|
17
|
+
return 'FAILED';
|
|
18
|
+
if (['completed', 'done'].includes(normalized))
|
|
19
|
+
return 'DONE';
|
|
20
|
+
if (['cancelled', 'canceled'].includes(normalized))
|
|
21
|
+
return 'STOPPED';
|
|
22
|
+
return 'IDLE';
|
|
23
|
+
}
|
|
24
|
+
function renderSubagentPanel(profiles, statuses) {
|
|
25
|
+
const statusByProfile = new Map();
|
|
26
|
+
for (const status of statuses) {
|
|
27
|
+
if (status.profile_id)
|
|
28
|
+
statusByProfile.set(status.profile_id, status);
|
|
29
|
+
}
|
|
30
|
+
const rows = profiles.map((profile) => {
|
|
31
|
+
const status = statusByProfile.get(profile.profile_id);
|
|
32
|
+
const profileId = boundedText(profile.profile_id, 22);
|
|
33
|
+
const name = boundedText(profile.name, 18);
|
|
34
|
+
const state = statusLabel(status?.status);
|
|
35
|
+
const progress = Number.isFinite(status?.progress) ? ` ${status?.progress}%` : '';
|
|
36
|
+
const action = boundedText(status?.current_action, 26);
|
|
37
|
+
return [
|
|
38
|
+
profileId.padEnd(22),
|
|
39
|
+
name.padEnd(18),
|
|
40
|
+
`${state}${progress}`,
|
|
41
|
+
action,
|
|
42
|
+
];
|
|
43
|
+
});
|
|
44
|
+
const headings = ['PROFILE ID', 'NAME', 'STATE', 'CURRENT ACTION'];
|
|
45
|
+
const widths = [22, 18, 16, 26];
|
|
46
|
+
const border = `+${widths.map((width) => '-'.repeat(width + 2)).join('+')}+`;
|
|
47
|
+
const row = (values) => `| ${values.map((value, index) => value.padEnd(widths[index])).join(' | ')} |`;
|
|
48
|
+
const lines = [
|
|
49
|
+
border,
|
|
50
|
+
row(headings),
|
|
51
|
+
border,
|
|
52
|
+
...(rows.length > 0
|
|
53
|
+
? rows.map(row)
|
|
54
|
+
: [row(['No profiles', 'available', '', ''])]),
|
|
55
|
+
border,
|
|
56
|
+
];
|
|
57
|
+
return lines.join('\n');
|
|
58
|
+
}
|
|
59
|
+
function asArray(value, label, isItem) {
|
|
60
|
+
if (!Array.isArray(value) || !value.every(isItem)) {
|
|
61
|
+
throw new Error(`Agent returned an invalid ${label} list.`);
|
|
62
|
+
}
|
|
63
|
+
return value;
|
|
64
|
+
}
|
|
65
|
+
function isSubagentProfile(value) {
|
|
66
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value))
|
|
67
|
+
return false;
|
|
68
|
+
const profile = value;
|
|
69
|
+
return typeof profile.profile_id === 'string'
|
|
70
|
+
&& typeof profile.name === 'string';
|
|
71
|
+
}
|
|
72
|
+
function isSubagentStatus(value) {
|
|
73
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value))
|
|
74
|
+
return false;
|
|
75
|
+
const status = value;
|
|
76
|
+
return (status.profile_id === undefined || typeof status.profile_id === 'string')
|
|
77
|
+
&& (status.status === undefined || typeof status.status === 'string')
|
|
78
|
+
&& (status.progress === undefined || typeof status.progress === 'number')
|
|
79
|
+
&& (status.current_action === undefined || typeof status.current_action === 'string');
|
|
80
|
+
}
|
|
81
|
+
async function handleSubagentsCommand(client, args = '') {
|
|
82
|
+
const trimmed = args.trim();
|
|
83
|
+
const chatMatch = trimmed.match(/^chat\s+(\S+)\s+([\s\S]+)$/i);
|
|
84
|
+
if (chatMatch) {
|
|
85
|
+
const [, profileId, message] = chatMatch;
|
|
86
|
+
try {
|
|
87
|
+
const result = await client.request('CHAT_SUBAGENT', { profile_id: profileId, message: message.trim() }, 120000);
|
|
88
|
+
console.log('\n' + (0, colors_1.CYAN_BOLD)(` SUBAGENT ${profileId}`));
|
|
89
|
+
if (result !== null
|
|
90
|
+
&& typeof result === 'object'
|
|
91
|
+
&& 'response' in result
|
|
92
|
+
&& typeof result.response === 'string') {
|
|
93
|
+
console.log(result.response);
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
console.log(JSON.stringify(result, null, 2));
|
|
97
|
+
}
|
|
98
|
+
console.log('');
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
console.log((0, colors_1.RED)(` No se pudo contactar al subagente: ${error.message}\n`));
|
|
102
|
+
}
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (trimmed && !/^list$/i.test(trimmed)) {
|
|
106
|
+
console.log((0, colors_1.YELLOW)(' Uso: /subagents [list] | /subagents chat <id> <mensaje>\n'));
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
const [profilesValue, statusesValue] = await Promise.all([
|
|
111
|
+
client.request('GET_SUBAGENT_PROFILES'),
|
|
112
|
+
client.request('GET_SUBAGENT_STATUS'),
|
|
113
|
+
]);
|
|
114
|
+
const profiles = asArray(profilesValue, 'subagent profile', isSubagentProfile);
|
|
115
|
+
const statuses = asArray(statusesValue, 'subagent status', isSubagentStatus);
|
|
116
|
+
console.log(`\n${(0, colors_1.CYAN_BOLD)(' HELIX SUBAGENTS')}`);
|
|
117
|
+
console.log(renderSubagentPanel(profiles, statuses));
|
|
118
|
+
console.log((0, colors_1.DIM)(' Chat: /subagents chat <profile_id> <mensaje>\n'));
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
console.log((0, colors_1.RED)(` No se pudo cargar el panel de subagentes: ${error.message}\n`));
|
|
122
|
+
}
|
|
123
|
+
}
|