helix-assistant-cli 0.2.14 → 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.
@@ -0,0 +1,167 @@
1
+ /**
2
+ * /memory — CLI command for inspecting the Adaptive Learning Harness.
3
+ *
4
+ * Subcommands:
5
+ * /memory — show quick stats summary
6
+ * /memory stats — detailed episode + rule counts
7
+ * /memory rules — list top learned rules
8
+ * /memory reset — run maintenance / pruning cycle
9
+ */
10
+ import chalk from 'chalk';
11
+ import type { AgentClient } from '../client/agent-client';
12
+ import { GREEN, YELLOW, RED, GRAY, DIM, BOLD, ORANGE_BOLD, CYAN } from '../ui/colors';
13
+
14
+ // ── Types ─────────────────────────────────────────────────────────────────────
15
+
16
+ interface LearnedRule {
17
+ category: string;
18
+ rule: string;
19
+ confidence: number;
20
+ uses: number;
21
+ }
22
+
23
+ interface LearningStats {
24
+ episode_count: number;
25
+ rule_count: number;
26
+ correction_count: number;
27
+ skill_count: number;
28
+ top_rules: LearnedRule[];
29
+ last_episode_at?: string;
30
+ user_md_exists: boolean;
31
+ }
32
+
33
+ // ── Request helper ────────────────────────────────────────────────────────────
34
+
35
+ type RequestableClient = AgentClient & {
36
+ request: <T>(type: string, payload: unknown) => Promise<T>;
37
+ };
38
+
39
+ async function fetchLearningStats(client: AgentClient): Promise<LearningStats | null> {
40
+ try {
41
+ return await (client as RequestableClient).request<LearningStats>('GET_LEARNING_STATS', {});
42
+ } catch {
43
+ return null;
44
+ }
45
+ }
46
+
47
+ // ── Renderers ─────────────────────────────────────────────────────────────────
48
+
49
+ function renderStats(stats: LearningStats): void {
50
+ console.log('');
51
+ console.log(` ${chalk.hex('#FF8C00').bold('⬡ Sistema de Aprendizaje Adaptativo')}`);
52
+ console.log(` ${chalk.gray('─'.repeat(48))}`);
53
+
54
+ const ep = stats.episode_count;
55
+ const ru = stats.rule_count;
56
+ const co = stats.correction_count;
57
+ const sk = stats.skill_count;
58
+
59
+ console.log(` ${chalk.gray('Episodios registrados')} ${chalk.white.bold(ep.toString())}`);
60
+ console.log(` ${chalk.gray('Correcciones del usuario')} ${co > 0 ? chalk.yellow.bold(co.toString()) : chalk.white(co.toString())}`);
61
+ console.log(` ${chalk.gray('Reglas aprendidas')} ${chalk.cyan.bold(ru.toString())}`);
62
+ console.log(` ${chalk.gray('Skills auto-generadas')} ${chalk.magenta.bold(sk.toString())}`);
63
+
64
+ if (stats.last_episode_at) {
65
+ const d = new Date(stats.last_episode_at);
66
+ console.log(` ${chalk.gray('Último episodio')} ${DIM(d.toLocaleString())}`);
67
+ }
68
+
69
+ const userMdStatus = stats.user_md_exists
70
+ ? chalk.green('✔ USER.md activo')
71
+ : chalk.gray('○ Sin perfil generado aún');
72
+ console.log(` ${chalk.gray('Perfil feedforward')} ${userMdStatus}`);
73
+ console.log('');
74
+ }
75
+
76
+ function renderRules(stats: LearningStats): void {
77
+ if (!stats.top_rules || stats.top_rules.length === 0) {
78
+ console.log(GRAY(' No hay reglas aprendidas todavía. Interactúa con HELIX para que aprenda tus preferencias.\n'));
79
+ return;
80
+ }
81
+
82
+ console.log('');
83
+ console.log(` ${chalk.hex('#FF8C00').bold(`✦ Top ${stats.top_rules.length} Reglas Aprendidas`)}`);
84
+ console.log(` ${chalk.gray('─'.repeat(56))}`);
85
+
86
+ for (const rule of stats.top_rules) {
87
+ const conf = Math.round(rule.confidence * 100);
88
+ const bar = '█'.repeat(Math.min(10, Math.floor(conf / 10)));
89
+ const confColor = conf >= 80 ? chalk.green : conf >= 50 ? chalk.yellow : chalk.gray;
90
+ console.log(` ${chalk.cyan(`[${rule.category}]`).padEnd(24)} ${chalk.white(rule.rule)}`);
91
+ console.log(` ${DIM('Confianza:')} ${confColor(`${conf}%`)} ${chalk.gray(bar)} ${DIM(`(${rule.uses} usos)`)}`);
92
+ console.log('');
93
+ }
94
+ }
95
+
96
+ // ── Command handlers ──────────────────────────────────────────────────────────
97
+
98
+ export async function handleMemoryStats(client: AgentClient): Promise<void> {
99
+ const stats = await fetchLearningStats(client);
100
+
101
+ if (!stats) {
102
+ console.log(YELLOW(' ⚠ El harness de aprendizaje no está disponible (agente desconectado o versión antigua).\n'));
103
+ return;
104
+ }
105
+
106
+ renderStats(stats);
107
+ if (stats.top_rules?.length) {
108
+ console.log(DIM(' Usa /memory rules para ver las reglas detalladas.'));
109
+ console.log('');
110
+ }
111
+ }
112
+
113
+ export async function handleMemoryRules(client: AgentClient): Promise<void> {
114
+ const stats = await fetchLearningStats(client);
115
+
116
+ if (!stats) {
117
+ console.log(YELLOW(' ⚠ El harness de aprendizaje no está disponible.\n'));
118
+ return;
119
+ }
120
+
121
+ renderStats(stats);
122
+ renderRules(stats);
123
+ }
124
+
125
+ export async function handleMemoryReset(client: AgentClient): Promise<void> {
126
+ try {
127
+ const result = await (client as RequestableClient).request<{ pruned_rules_count: number; pruned_skills: string[] }>(
128
+ 'RUN_LEARNING_MAINTENANCE',
129
+ {},
130
+ );
131
+
132
+ console.log('');
133
+ console.log(GREEN(' ✔ Mantenimiento del harness completado.'));
134
+ console.log(` ${chalk.gray('Reglas podadas:')} ${chalk.white.bold(String(result.pruned_rules_count ?? 0))}`);
135
+ const pruned = result.pruned_skills ?? [];
136
+ if (pruned.length) {
137
+ console.log(` ${chalk.gray('Skills podadas:')} ${chalk.white(pruned.join(', '))}`);
138
+ } else {
139
+ console.log(` ${chalk.gray('Skills podadas:')} ${DIM('ninguna')}`);
140
+ }
141
+ console.log('');
142
+ } catch (err) {
143
+ console.log(RED(` ✖ Error durante mantenimiento: ${(err as Error).message}\n`));
144
+ }
145
+ }
146
+
147
+ /** Top-level dispatcher for /memory <args> */
148
+ export async function handleMemoryCommand(client: AgentClient, args: string): Promise<void> {
149
+ const sub = args.trim().split(/\s+/)[0]?.toLowerCase() || '';
150
+
151
+ switch (sub) {
152
+ case '':
153
+ case 'stats':
154
+ await handleMemoryStats(client);
155
+ break;
156
+ case 'rules':
157
+ await handleMemoryRules(client);
158
+ break;
159
+ case 'reset':
160
+ case 'maintenance':
161
+ case 'prune':
162
+ await handleMemoryReset(client);
163
+ break;
164
+ default:
165
+ console.log(YELLOW(` Subcomando desconocido: "${sub}". Opciones: stats, rules, reset\n`));
166
+ }
167
+ }
@@ -0,0 +1,237 @@
1
+ /**
2
+ * /model — CLI command for viewing and switching the active AI model.
3
+ *
4
+ * Subcommands:
5
+ * /model — show current model + provider
6
+ * /model list — list all model tiers (Free, Local Ollama, Cloud)
7
+ * /model set <id> — switch to a model by id
8
+ * /model effort <low|medium|high> — set effort/quality level
9
+ */
10
+ import chalk from 'chalk';
11
+ import type { AgentClient } from '../client/agent-client';
12
+ import { CYAN, CYAN_BOLD, GREEN, YELLOW, GRAY, DIM, BOLD, ORANGE_BOLD, RED } from '../ui/colors';
13
+
14
+ // ── Model catalog (mirrors ModelSelectorDropdown.tsx) ──────────────────────────
15
+ export interface ModelEntry {
16
+ id: string;
17
+ label: string;
18
+ tier: 'free' | 'local' | 'cloud';
19
+ provider: string;
20
+ badge?: string;
21
+ }
22
+
23
+ export const FREE_MODELS: ModelEntry[] = [
24
+ {
25
+ id: 'meta-llama/llama-3.2-3b-instruct:free',
26
+ label: 'Llama 3.2 3B Instruct',
27
+ tier: 'free',
28
+ provider: 'free',
29
+ badge: '0-cost',
30
+ },
31
+ {
32
+ id: 'qwen/qwen-2.5-coder-32b-instruct:free',
33
+ label: 'Qwen 2.5 Coder 32B',
34
+ tier: 'free',
35
+ provider: 'free',
36
+ badge: '0-cost',
37
+ },
38
+ {
39
+ id: 'deepseek/deepseek-r1:free',
40
+ label: 'DeepSeek R1',
41
+ tier: 'free',
42
+ provider: 'free',
43
+ badge: '0-cost · razonamiento',
44
+ },
45
+ {
46
+ id: 'google/gemini-flash-1.5:free',
47
+ label: 'Gemini Flash 1.5',
48
+ tier: 'free',
49
+ provider: 'free',
50
+ badge: '0-cost · rápido',
51
+ },
52
+ ];
53
+
54
+ export const LOCAL_MODELS: ModelEntry[] = [
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
+
62
+ export const CLOUD_MODELS: ModelEntry[] = [
63
+ { id: 'gpt-4o', label: 'GPT-4o', tier: 'cloud', provider: 'openai' },
64
+ { id: 'gpt-4o-mini', label: 'GPT-4o Mini', tier: 'cloud', provider: 'openai' },
65
+ { id: 'claude-3-5-sonnet-20241022', label: 'Claude 3.5 Sonnet', tier: 'cloud', provider: 'anthropic' },
66
+ { id: 'claude-3-haiku-20240307', label: 'Claude 3 Haiku', tier: 'cloud', provider: 'anthropic' },
67
+ { id: 'gemini-1.5-pro', label: 'Gemini 1.5 Pro', tier: 'cloud', provider: 'gemini' },
68
+ { id: 'gemini-1.5-flash', label: 'Gemini 1.5 Flash', tier: 'cloud', provider: 'gemini' },
69
+ ];
70
+
71
+ export const ALL_MODELS: ModelEntry[] = [...FREE_MODELS, ...LOCAL_MODELS, ...CLOUD_MODELS];
72
+
73
+ // ── Helpers ───────────────────────────────────────────────────────────────────
74
+
75
+ function providerBadge(entry: ModelEntry): string {
76
+ if (entry.tier === 'free') return chalk.green.bold('[0-COST]');
77
+ if (entry.tier === 'local') return chalk.cyan.bold('[LOCAL]');
78
+ return chalk.yellow.bold('[CLOUD]');
79
+ }
80
+
81
+ function findModel(id: string): ModelEntry | undefined {
82
+ return ALL_MODELS.find((m) => m.id === id || m.id.startsWith(id));
83
+ }
84
+
85
+ // ── Command handlers ──────────────────────────────────────────────────────────
86
+
87
+ /** /model — show current model */
88
+ export async function handleModelStatus(client: AgentClient): Promise<void> {
89
+ try {
90
+ const settings = await client.getSettings();
91
+ const modelId: string = (settings as Record<string, string>).default_model || '(none)';
92
+ const provider: string = (settings as Record<string, string>).preferred_provider || '(none)';
93
+ const entry = findModel(modelId);
94
+
95
+ console.log('');
96
+ console.log(` ${chalk.hex('#FF8C00').bold('⬡ Modelo Activo')}`);
97
+ console.log(` ${chalk.gray('─'.repeat(44))}`);
98
+ console.log(` ${chalk.gray('ID')} ${chalk.white.bold(modelId)}`);
99
+ console.log(` ${chalk.gray('Proveedor')} ${chalk.white(provider)}`);
100
+ if (entry) {
101
+ console.log(` ${chalk.gray('Nombre')} ${entry.label}`);
102
+ console.log(` ${chalk.gray('Nivel')} ${providerBadge(entry)} ${entry.badge ? DIM(`(${entry.badge})`) : ''}`);
103
+ }
104
+ const speed: string = (settings as Record<string, string>).speed_preference || 'balanced';
105
+ const quality: string = (settings as Record<string, string>).quality_preference || 'balanced';
106
+ console.log(` ${chalk.gray('Velocidad')} ${chalk.cyan(speed)}`);
107
+ console.log(` ${chalk.gray('Calidad')} ${chalk.cyan(quality)}`);
108
+ console.log('');
109
+ console.log(DIM(' Usa /model list para ver opciones, /model set <id> para cambiar.'));
110
+ console.log('');
111
+ } catch (err) {
112
+ console.log(RED(` Error al obtener modelo: ${(err as Error).message}\n`));
113
+ }
114
+ }
115
+
116
+ /** /model list — print the full model catalog */
117
+ export function handleModelList(): void {
118
+ const printSection = (title: string, models: ModelEntry[]): void => {
119
+ console.log(`\n ${chalk.hex('#FF8C00').bold(title)}`);
120
+ console.log(` ${chalk.gray('─'.repeat(60))}`);
121
+ for (const m of models) {
122
+ const badge = providerBadge(m);
123
+ const extra = m.badge ? DIM(` · ${m.badge}`) : '';
124
+ console.log(` ${badge} ${chalk.white.bold(m.label.padEnd(32))} ${GRAY(m.id)}${extra}`);
125
+ }
126
+ };
127
+
128
+ console.log('');
129
+ console.log(` ${BOLD('🧠 Catálogo de Modelos HELIX')}`);
130
+ printSection('✦ Nivel 1 — Sin costo (OpenRouter Free)', FREE_MODELS);
131
+ printSection('✦ Nivel 2 — Local Ollama (sin internet)', LOCAL_MODELS);
132
+ printSection('✦ Nivel 3 — Nube con API Key propia', CLOUD_MODELS);
133
+ console.log('');
134
+ console.log(DIM(' Usa: /model set <model-id> para activar un modelo'));
135
+ console.log(DIM(' Usa: /model effort <low|medium|high> para ajustar esfuerzo'));
136
+ console.log('');
137
+ }
138
+
139
+ /** /model set <id> — switch the active model */
140
+ export async function handleModelSet(client: AgentClient, modelId: string): Promise<void> {
141
+ if (!modelId) {
142
+ console.log(YELLOW(' Uso: /model set <model-id>\n'));
143
+ return;
144
+ }
145
+
146
+ const entry = findModel(modelId);
147
+ const provider = entry?.provider || (modelId.startsWith('ollama/') ? 'ollama' : 'openai');
148
+
149
+ try {
150
+ await client.saveSettings({
151
+ default_model: entry?.id ?? modelId,
152
+ preferred_provider: provider,
153
+ } as Parameters<typeof client.saveSettings>[0]);
154
+
155
+ const badge = entry ? providerBadge(entry) : '';
156
+ console.log('');
157
+ console.log(GREEN(' ✔ Modelo actualizado correctamente.'));
158
+ console.log(` ${chalk.gray('Modelo:')} ${chalk.white.bold(entry?.id ?? modelId)} ${badge}`);
159
+ console.log(` ${chalk.gray('Proveedor:')} ${chalk.white(provider)}`);
160
+ console.log('');
161
+ } catch (err) {
162
+ console.log(RED(` ✖ Error al cambiar modelo: ${(err as Error).message}\n`));
163
+ }
164
+ }
165
+
166
+ /** /model effort <low|medium|high> — set effort/quality level */
167
+ export async function handleModelEffort(client: AgentClient, level: string): Promise<void> {
168
+ const valid = ['low', 'medium', 'high'];
169
+ const normalized = level?.toLowerCase();
170
+ if (!normalized || !valid.includes(normalized)) {
171
+ console.log(YELLOW(' Uso: /model effort <low|medium|high>\n'));
172
+ return;
173
+ }
174
+
175
+ const speedMap: Record<string, string> = { low: 'fast', medium: 'balanced', high: 'balanced' };
176
+ const qualityMap: Record<string, string> = { low: 'fast', medium: 'balanced', high: 'best' };
177
+
178
+ try {
179
+ await client.saveSettings({
180
+ speed_preference: speedMap[normalized],
181
+ quality_preference: qualityMap[normalized],
182
+ } as Parameters<typeof client.saveSettings>[0]);
183
+
184
+ console.log('');
185
+ console.log(GREEN(` ✔ Nivel de esfuerzo → ${chalk.white.bold(normalized.toUpperCase())}`));
186
+ console.log(DIM(` Velocidad: ${speedMap[normalized]} | Calidad: ${qualityMap[normalized]}`));
187
+ console.log('');
188
+ } catch (err) {
189
+ console.log(RED(` ✖ Error al ajustar esfuerzo: ${(err as Error).message}\n`));
190
+ }
191
+ }
192
+
193
+ /** /model free-list — list free models fetched from bridge (live) */
194
+ export async function handleFreeModelList(client: AgentClient): Promise<void> {
195
+ try {
196
+ const result = await (client as AgentClient & { request: <T>(type: string, payload: unknown) => Promise<T> })
197
+ .request<{ models: Array<{ id: string; label: string; provider: string }> }>('LIST_FREE_MODELS', {});
198
+
199
+ if (!result?.models?.length) {
200
+ // Fallback to static catalog
201
+ handleModelList();
202
+ return;
203
+ }
204
+
205
+ console.log(`\n ${BOLD('✦ Modelos gratuitos disponibles (live):')} \n`);
206
+ for (const m of result.models) {
207
+ console.log(` ${GREEN('[0-COST]')} ${chalk.white.bold(m.label)} ${GRAY(m.id)}`);
208
+ }
209
+ console.log('');
210
+ } catch {
211
+ // Agent not connected or handler not present — fall back to static
212
+ handleModelList();
213
+ }
214
+ }
215
+
216
+ /** Top-level dispatcher for /model <args> */
217
+ export async function handleModelCommand(client: AgentClient, args: string): Promise<void> {
218
+ const parts = args.trim().split(/\s+/);
219
+ const sub = parts[0]?.toLowerCase() || '';
220
+
221
+ switch (sub) {
222
+ case '':
223
+ await handleModelStatus(client);
224
+ break;
225
+ case 'list':
226
+ handleModelList();
227
+ break;
228
+ case 'set':
229
+ await handleModelSet(client, parts.slice(1).join(' '));
230
+ break;
231
+ case 'effort':
232
+ await handleModelEffort(client, parts[1] || '');
233
+ break;
234
+ default:
235
+ console.log(YELLOW(` Subcomando desconocido: "${sub}". Opciones: list, set, effort\n`));
236
+ }
237
+ }
@@ -0,0 +1,150 @@
1
+ import type { AgentClient } from '../client/agent-client';
2
+ import { CYAN_BOLD, DIM, RED, YELLOW } from '../ui/colors';
3
+
4
+ interface SubagentProfile {
5
+ profile_id: string;
6
+ name: string;
7
+ }
8
+
9
+ interface SubagentStatus {
10
+ profile_id?: string;
11
+ name?: string;
12
+ status?: string;
13
+ progress?: number;
14
+ current_action?: string;
15
+ }
16
+
17
+ function boundedText(value: unknown, maxLength: number): string {
18
+ if (typeof value !== 'string') return '';
19
+ const clean = value.replace(/[\r\n|]/g, ' ').trim();
20
+ return clean.length > maxLength ? `${clean.slice(0, maxLength - 3)}...` : clean;
21
+ }
22
+
23
+ function statusLabel(status?: string): string {
24
+ const normalized = status?.toLowerCase() || 'idle';
25
+ if (['running', 'active', 'in_progress'].includes(normalized)) return 'RUNNING';
26
+ if (['failed', 'error'].includes(normalized)) return 'FAILED';
27
+ if (['completed', 'done'].includes(normalized)) return 'DONE';
28
+ if (['cancelled', 'canceled'].includes(normalized)) return 'STOPPED';
29
+ return 'IDLE';
30
+ }
31
+
32
+ export function renderSubagentPanel(
33
+ profiles: SubagentProfile[],
34
+ statuses: SubagentStatus[],
35
+ ): string {
36
+ const statusByProfile = new Map<string, SubagentStatus>();
37
+ for (const status of statuses) {
38
+ if (status.profile_id) statusByProfile.set(status.profile_id, status);
39
+ }
40
+
41
+ const rows = profiles.map((profile) => {
42
+ const status = statusByProfile.get(profile.profile_id);
43
+ const profileId = boundedText(profile.profile_id, 22);
44
+ const name = boundedText(profile.name, 18);
45
+ const state = statusLabel(status?.status);
46
+ const progress = Number.isFinite(status?.progress) ? ` ${status?.progress}%` : '';
47
+ const action = boundedText(status?.current_action, 26);
48
+ return [
49
+ profileId.padEnd(22),
50
+ name.padEnd(18),
51
+ `${state}${progress}`,
52
+ action,
53
+ ];
54
+ });
55
+
56
+ const headings = ['PROFILE ID', 'NAME', 'STATE', 'CURRENT ACTION'];
57
+ const widths = [22, 18, 16, 26];
58
+ const border = `+${widths.map((width) => '-'.repeat(width + 2)).join('+')}+`;
59
+ const row = (values: string[]) =>
60
+ `| ${values.map((value, index) => value.padEnd(widths[index])).join(' | ')} |`;
61
+ const lines = [
62
+ border,
63
+ row(headings),
64
+ border,
65
+ ...(rows.length > 0
66
+ ? rows.map(row)
67
+ : [row(['No profiles', 'available', '', ''])]),
68
+ border,
69
+ ];
70
+ return lines.join('\n');
71
+ }
72
+
73
+ function asArray<T>(
74
+ value: unknown,
75
+ label: string,
76
+ isItem: (item: unknown) => item is T,
77
+ ): T[] {
78
+ if (!Array.isArray(value) || !value.every(isItem)) {
79
+ throw new Error(`Agent returned an invalid ${label} list.`);
80
+ }
81
+ return value;
82
+ }
83
+
84
+ function isSubagentProfile(value: unknown): value is SubagentProfile {
85
+ if (value === null || typeof value !== 'object' || Array.isArray(value)) return false;
86
+ const profile = value as Record<string, unknown>;
87
+ return typeof profile.profile_id === 'string'
88
+ && typeof profile.name === 'string';
89
+ }
90
+
91
+ function isSubagentStatus(value: unknown): value is SubagentStatus {
92
+ if (value === null || typeof value !== 'object' || Array.isArray(value)) return false;
93
+ const status = value as Record<string, unknown>;
94
+ return (status.profile_id === undefined || typeof status.profile_id === 'string')
95
+ && (status.status === undefined || typeof status.status === 'string')
96
+ && (status.progress === undefined || typeof status.progress === 'number')
97
+ && (status.current_action === undefined || typeof status.current_action === 'string');
98
+ }
99
+
100
+ export async function handleSubagentsCommand(
101
+ client: AgentClient,
102
+ args = '',
103
+ ): Promise<void> {
104
+ const trimmed = args.trim();
105
+ const chatMatch = trimmed.match(/^chat\s+(\S+)\s+([\s\S]+)$/i);
106
+ if (chatMatch) {
107
+ const [, profileId, message] = chatMatch;
108
+ try {
109
+ const result = await client.request<unknown>(
110
+ 'CHAT_SUBAGENT',
111
+ { profile_id: profileId, message: message.trim() },
112
+ 120000,
113
+ );
114
+ console.log('\n' + CYAN_BOLD(` SUBAGENT ${profileId}`));
115
+ if (
116
+ result !== null
117
+ && typeof result === 'object'
118
+ && 'response' in result
119
+ && typeof result.response === 'string'
120
+ ) {
121
+ console.log(result.response);
122
+ } else {
123
+ console.log(JSON.stringify(result, null, 2));
124
+ }
125
+ console.log('');
126
+ } catch (error) {
127
+ console.log(RED(` No se pudo contactar al subagente: ${(error as Error).message}\n`));
128
+ }
129
+ return;
130
+ }
131
+
132
+ if (trimmed && !/^list$/i.test(trimmed)) {
133
+ console.log(YELLOW(' Uso: /subagents [list] | /subagents chat <id> <mensaje>\n'));
134
+ return;
135
+ }
136
+
137
+ try {
138
+ const [profilesValue, statusesValue] = await Promise.all([
139
+ client.request<unknown[]>('GET_SUBAGENT_PROFILES'),
140
+ client.request<unknown[]>('GET_SUBAGENT_STATUS'),
141
+ ]);
142
+ const profiles = asArray(profilesValue, 'subagent profile', isSubagentProfile);
143
+ const statuses = asArray(statusesValue, 'subagent status', isSubagentStatus);
144
+ console.log(`\n${CYAN_BOLD(' HELIX SUBAGENTS')}`);
145
+ console.log(renderSubagentPanel(profiles, statuses));
146
+ console.log(DIM(' Chat: /subagents chat <profile_id> <mensaje>\n'));
147
+ } catch (error) {
148
+ console.log(RED(` No se pudo cargar el panel de subagentes: ${(error as Error).message}\n`));
149
+ }
150
+ }
@@ -9,10 +9,19 @@ export interface McpDefaultDefinition {
9
9
 
10
10
  /** Catalog only: entries are never enabled or installed automatically. */
11
11
  export const MCP_DEFAULTS: McpDefaultDefinition[] = [
12
- { id: 'filesystem', label: 'Archivos locales', purpose: 'Carpeta elegida por el usuario', transport: 'stdio', activation: 'local-opt-in', privacy: 'Solo rutas autorizadas' },
12
+ { id: 'filesystem', label: 'Archivos locales', purpose: 'Carpetas y archivos del equipo del usuario', transport: 'stdio', activation: 'local-opt-in', privacy: 'Solo rutas autorizadas' },
13
13
  { id: 'obsidian', label: 'Obsidian / Helix Vault', purpose: 'Leer y gestionar una bóveda concreta', transport: 'stdio', activation: 'local-opt-in', privacy: 'Solo la bóveda seleccionada' },
14
14
  { id: 'vscode', label: 'VS Code', purpose: 'Trabajar con el workspace seleccionado', transport: 'stdio', activation: 'local-opt-in', privacy: 'Solo el workspace seleccionado' },
15
- { id: 'github', label: 'GitHub', purpose: 'Repositorios, issues y pull requests', transport: 'stdio', activation: 'oauth-opt-in', privacy: 'Requiere token u OAuth del usuario' },
16
- { id: 'gmail', label: 'Gmail', purpose: 'Correo y búsqueda de mensajes', transport: 'connector', activation: 'oauth-opt-in', privacy: 'Requiere OAuth explícito del usuario' },
17
- { id: 'notion', label: 'Notion', purpose: 'Páginas y bases de datos', transport: 'connector', activation: 'oauth-opt-in', privacy: 'Requiere integración autorizada' },
15
+ { id: 'cursor', label: 'Cursor', purpose: 'Proyectos y editor de código del usuario', transport: 'stdio', activation: 'local-opt-in', privacy: 'Solo el workspace autorizado' },
16
+ { id: 'github', label: 'GitHub', purpose: 'Repositorios, issues, pull requests y código', transport: 'stdio', activation: 'oauth-opt-in', privacy: 'Requiere token u OAuth del usuario' },
17
+ { id: 'linear', label: 'Linear', purpose: 'Issues, roadmap y trabajo del equipo', transport: 'connector', activation: 'oauth-opt-in', privacy: 'Requiere acceso autorizado del workspace' },
18
+ { id: 'gmail', label: 'Gmail', purpose: 'Correo, búsquedas y mensajes relevantes', transport: 'connector', activation: 'oauth-opt-in', privacy: 'Requiere OAuth explícito del usuario' },
19
+ { id: 'google-drive', label: 'Google Drive', purpose: 'Documentos y archivos compartidos', transport: 'connector', activation: 'oauth-opt-in', privacy: 'Requiere acceso autorizado a la cuenta' },
20
+ { id: 'notion', label: 'Notion', purpose: 'Páginas, bases de datos y conocimiento', transport: 'connector', activation: 'oauth-opt-in', privacy: 'Requiere integración autorizada' },
21
+ { id: 'chatgpt', label: 'ChatGPT', purpose: 'Acceso a tareas y contexto de chat del usuario', transport: 'connector', activation: 'oauth-opt-in', privacy: 'Requiere conexión autorizada del usuario' },
22
+ { id: 'claude-desktop', label: 'Claude Desktop', purpose: 'Contexto y trabajo en la app desktop de Claude', transport: 'stdio', activation: 'local-opt-in', privacy: 'Solo los recursos autorizados por el usuario' },
23
+ { id: 'word', label: 'Microsoft Word', purpose: 'Documentos y edición de texto', transport: 'stdio', activation: 'local-opt-in', privacy: 'Solo archivos autorizados' },
24
+ { id: 'excel', label: 'Microsoft Excel', purpose: 'Hojas de cálculo, análisis y tabulación', transport: 'stdio', activation: 'local-opt-in', privacy: 'Solo archivos autorizados' },
25
+ { id: 'powerpoint', label: 'Microsoft PowerPoint', purpose: 'Presentaciones y material visual', transport: 'stdio', activation: 'local-opt-in', privacy: 'Solo archivos autorizados' },
26
+ { id: 'antigravity', label: 'Antigravity', purpose: 'Workspace y flujos de automatización del usuario', transport: 'connector', activation: 'oauth-opt-in', privacy: 'Requiere conexión explícita del usuario' },
18
27
  ];
@@ -0,0 +1,23 @@
1
+ // Declaration shims for modules without bundled @types in this workspace.
2
+ // These are minimal stubs to satisfy TypeScript when explicit "types" is set.
3
+
4
+ declare module 'prompts' {
5
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
6
+ function prompts(questions: any | any[]): Promise<Record<string, any>>;
7
+ export = prompts;
8
+ }
9
+
10
+ declare module 'jpeg-js' {
11
+ interface RawImageData {
12
+ width: number;
13
+ height: number;
14
+ data: Buffer | Uint8Array;
15
+ }
16
+ interface DecodeResult extends RawImageData {
17
+ comments?: string[];
18
+ }
19
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
20
+ function decode(jpegData: Buffer | Uint8Array, opts?: Record<string, any>): DecodeResult;
21
+ function encode(rawImageData: RawImageData, quality?: number): { data: Buffer; width: number; height: number };
22
+ export { decode, encode };
23
+ }
package/src/index.ts CHANGED
@@ -32,6 +32,7 @@ import type { HelixSettings } from './client/types';
32
32
  import { handleShowApiKey } from './commands/apikey';
33
33
  import { ORANGE, CYAN, GREEN, RED, BOLD, DIM } from './ui/colors';
34
34
  import { getAsciiLogo } from './ui/banner';
35
+ import packageJson from '../package.json';
35
36
 
36
37
  async function readStdin(): Promise<string> {
37
38
  if (process.stdin.isTTY) {
@@ -73,7 +74,7 @@ const program = new Command();
73
74
  program
74
75
  .name('helix')
75
76
  .description('HELIX - Interactive Terminal CLI for Windows AI Desktop Agent')
76
- .version('0.2.14')
77
+ .version(packageJson.version)
77
78
  .option('-u, --ws-url <url>', 'URL del servidor WebSocket del agente', getWsUrl())
78
79
  .option('-t, --token <token>', 'Token de autenticación WebSocket de HELIX', getWsToken())
79
80
  .option('--plain', 'Desactivar formato enriquecido de Markdown y mostrar texto plano sin asteriscos')