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 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
- killProcessOnPort(port);
295
- await new Promise((resolve) => setTimeout(resolve, 500));
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: true,
295
+ detached: process.platform !== 'win32',
314
296
  stdio: 'ignore', // run completely in background
315
297
  windowsHide: true,
316
298
  env: {
@@ -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}`, `browser_${Date.now()}`);
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')}\n`);
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
+ }