helix-assistant-cli 0.0.0-stage → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/bin/helix.js +3 -0
  2. package/dist/client/agent-client.d.ts +60 -0
  3. package/dist/client/agent-client.js +322 -0
  4. package/dist/client/agent-launcher.d.ts +30 -0
  5. package/dist/client/agent-launcher.js +339 -0
  6. package/dist/client/types.d.ts +88 -0
  7. package/dist/client/types.js +2 -0
  8. package/dist/commands/apikey.d.ts +3 -0
  9. package/dist/commands/apikey.js +123 -0
  10. package/dist/commands/browser.d.ts +2 -0
  11. package/dist/commands/browser.js +35 -0
  12. package/dist/commands/mcp.d.ts +3 -0
  13. package/dist/commands/mcp.js +102 -0
  14. package/dist/commands/mic.d.ts +2 -0
  15. package/dist/commands/mic.js +57 -0
  16. package/dist/commands/skills.d.ts +3 -0
  17. package/dist/commands/skills.js +105 -0
  18. package/dist/config/env.d.ts +6 -0
  19. package/dist/config/env.js +71 -0
  20. package/dist/index.d.ts +1 -0
  21. package/dist/index.js +126 -0
  22. package/dist/ui/autocomplete.d.ts +25 -0
  23. package/dist/ui/autocomplete.js +142 -0
  24. package/dist/ui/banner.d.ts +24 -0
  25. package/dist/ui/banner.js +207 -0
  26. package/dist/ui/chat.d.ts +1 -0
  27. package/dist/ui/chat.js +5 -0
  28. package/dist/ui/colors.d.ts +34 -0
  29. package/dist/ui/colors.js +69 -0
  30. package/dist/ui/repl.d.ts +31 -0
  31. package/dist/ui/repl.js +402 -0
  32. package/package.json +31 -4
  33. package/src/client/agent-client.ts +387 -0
  34. package/src/client/agent-launcher.ts +374 -0
  35. package/src/client/types.ts +95 -0
  36. package/src/commands/apikey.ts +129 -0
  37. package/src/commands/browser.ts +39 -0
  38. package/src/commands/mcp.ts +105 -0
  39. package/src/commands/mic.ts +57 -0
  40. package/src/commands/skills.ts +109 -0
  41. package/src/config/env.ts +63 -0
  42. package/src/index.ts +139 -0
  43. package/src/ui/autocomplete.ts +153 -0
  44. package/src/ui/banner.ts +240 -0
  45. package/src/ui/chat.ts +1 -0
  46. package/src/ui/colors.ts +70 -0
  47. package/src/ui/repl.ts +473 -0
  48. package/tests/cli.test.ts +141 -0
  49. package/tsconfig.json +18 -0
  50. package/tsconfig.test.json +7 -0
  51. package/README.md +0 -3
@@ -0,0 +1,339 @@
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.getTokenFilePath = getTokenFilePath;
7
+ exports.getOrGenerateWsToken = getOrGenerateWsToken;
8
+ exports.isAgentProcessRunning = isAgentProcessRunning;
9
+ exports.isAgentStarting = isAgentStarting;
10
+ exports.findPythonAgentScript = findPythonAgentScript;
11
+ exports.findPythonExecutable = findPythonExecutable;
12
+ exports.probePort = probePort;
13
+ exports.probeWebSocketAuth = probeWebSocketAuth;
14
+ exports.killProcessOnPort = killProcessOnPort;
15
+ exports.waitForAgentReady = waitForAgentReady;
16
+ exports.spawnAgentDaemon = spawnAgentDaemon;
17
+ const child_process_1 = require("child_process");
18
+ const path_1 = __importDefault(require("path"));
19
+ const fs_1 = __importDefault(require("fs"));
20
+ const crypto_1 = __importDefault(require("crypto"));
21
+ const net_1 = __importDefault(require("net"));
22
+ const ws_1 = __importDefault(require("ws"));
23
+ let spawnedAgentProcess = null;
24
+ let activeSpawnToken = null;
25
+ let startupPromise = null;
26
+ function getTokenFilePath() {
27
+ const appData = process.env.APPDATA ||
28
+ (process.platform === 'darwin'
29
+ ? path_1.default.join(process.env.HOME || '', 'Library/Application Support')
30
+ : path_1.default.join(process.env.HOME || '', '.config'));
31
+ const helixDir = path_1.default.join(appData, 'HELIX');
32
+ if (!fs_1.default.existsSync(helixDir)) {
33
+ try {
34
+ fs_1.default.mkdirSync(helixDir, { recursive: true });
35
+ }
36
+ catch { }
37
+ }
38
+ return path_1.default.join(helixDir, '.ws_token');
39
+ }
40
+ function getOrGenerateWsToken() {
41
+ if (activeSpawnToken)
42
+ return activeSpawnToken;
43
+ if (process.env.HELIX_WS_TOKEN) {
44
+ activeSpawnToken = process.env.HELIX_WS_TOKEN;
45
+ return activeSpawnToken;
46
+ }
47
+ const tokenFile = getTokenFilePath();
48
+ if (fs_1.default.existsSync(tokenFile)) {
49
+ try {
50
+ const saved = fs_1.default.readFileSync(tokenFile, 'utf-8').trim();
51
+ if (saved.length >= 16) {
52
+ activeSpawnToken = saved;
53
+ process.env.HELIX_WS_TOKEN = activeSpawnToken;
54
+ return activeSpawnToken;
55
+ }
56
+ }
57
+ catch { }
58
+ }
59
+ activeSpawnToken = crypto_1.default.randomBytes(32).toString('hex');
60
+ process.env.HELIX_WS_TOKEN = activeSpawnToken;
61
+ try {
62
+ fs_1.default.writeFileSync(tokenFile, activeSpawnToken, { encoding: 'utf-8', mode: 0o600 });
63
+ }
64
+ catch { }
65
+ return activeSpawnToken;
66
+ }
67
+ function isAgentProcessRunning() {
68
+ return spawnedAgentProcess !== null && !spawnedAgentProcess.killed;
69
+ }
70
+ function isAgentStarting() {
71
+ return startupPromise !== null;
72
+ }
73
+ function findPythonAgentScript() {
74
+ const candidates = [
75
+ path_1.default.resolve(process.cwd(), 'services/agent/main.py'),
76
+ path_1.default.resolve(__dirname, '../../../services/agent/main.py'),
77
+ path_1.default.resolve(__dirname, '../../../../services/agent/main.py'),
78
+ path_1.default.resolve(__dirname, '../../services/agent/main.py'),
79
+ ];
80
+ for (const candidate of candidates) {
81
+ if (fs_1.default.existsSync(candidate)) {
82
+ return candidate;
83
+ }
84
+ }
85
+ return null;
86
+ }
87
+ function findPythonExecutable(repoRoot) {
88
+ // Check explicit environment variable first
89
+ if (process.env.PYTHON_PATH && fs_1.default.existsSync(process.env.PYTHON_PATH)) {
90
+ return process.env.PYTHON_PATH;
91
+ }
92
+ // On Windows, prefer pythonw.exe so NO console terminal window is opened
93
+ if (process.platform === 'win32') {
94
+ const venvGuiCandidates = [
95
+ path_1.default.join(repoRoot, '.venv', 'Scripts', 'pythonw.exe'),
96
+ path_1.default.join(repoRoot, 'venv', 'Scripts', 'pythonw.exe'),
97
+ path_1.default.join(repoRoot, 'services', 'agent', '.venv', 'Scripts', 'pythonw.exe'),
98
+ ];
99
+ for (const venvGui of venvGuiCandidates) {
100
+ if (fs_1.default.existsSync(venvGui)) {
101
+ return venvGui;
102
+ }
103
+ }
104
+ }
105
+ // Check virtual environment candidates relative to repository root
106
+ const venvCandidates = [
107
+ path_1.default.join(repoRoot, '.venv', 'Scripts', 'python.exe'),
108
+ path_1.default.join(repoRoot, '.venv', 'bin', 'python'),
109
+ path_1.default.join(repoRoot, 'venv', 'Scripts', 'python.exe'),
110
+ path_1.default.join(repoRoot, 'venv', 'bin', 'python'),
111
+ path_1.default.join(repoRoot, 'services', 'agent', '.venv', 'Scripts', 'python.exe'),
112
+ path_1.default.join(repoRoot, 'services', 'agent', '.venv', 'bin', 'python'),
113
+ ];
114
+ for (const venvPython of venvCandidates) {
115
+ if (fs_1.default.existsSync(venvPython)) {
116
+ return venvPython;
117
+ }
118
+ }
119
+ // Fallback to system PATH executables
120
+ const systemCandidates = process.platform === 'win32'
121
+ ? ['pythonw', 'python', 'py']
122
+ : ['python', 'python3', 'py'];
123
+ return systemCandidates[0];
124
+ }
125
+ /**
126
+ * Probes a TCP port quietly without initiating a WebSocket handshake.
127
+ */
128
+ function probePort(port, host = '127.0.0.1', timeoutMs = 800) {
129
+ return new Promise((resolve) => {
130
+ const socket = new net_1.default.Socket();
131
+ let settled = false;
132
+ socket.setTimeout(timeoutMs);
133
+ socket.once('connect', () => {
134
+ if (!settled) {
135
+ settled = true;
136
+ socket.destroy();
137
+ resolve(true);
138
+ }
139
+ });
140
+ socket.once('timeout', () => {
141
+ if (!settled) {
142
+ settled = true;
143
+ socket.destroy();
144
+ resolve(false);
145
+ }
146
+ });
147
+ socket.once('error', () => {
148
+ if (!settled) {
149
+ settled = true;
150
+ socket.destroy();
151
+ resolve(false);
152
+ }
153
+ });
154
+ try {
155
+ socket.connect(port, host);
156
+ }
157
+ catch {
158
+ if (!settled) {
159
+ settled = true;
160
+ resolve(false);
161
+ }
162
+ }
163
+ });
164
+ }
165
+ /**
166
+ * Probes the WebSocket endpoint with Authorization header.
167
+ * Returns:
168
+ * 'ready': 101 Switching Protocols (authenticated and connected)
169
+ * 'unauthorized': 401 Unauthorized (wrong token / rogue process on port)
170
+ * 'closed': Connection refused / port closed
171
+ */
172
+ function probeWebSocketAuth(port = 8765, token, host = '127.0.0.1', timeoutMs = 1000) {
173
+ return new Promise((resolve) => {
174
+ let settled = false;
175
+ let timer = null;
176
+ const headers = {};
177
+ const effectiveToken = token || getOrGenerateWsToken();
178
+ if (effectiveToken) {
179
+ headers['Authorization'] = `Bearer ${effectiveToken}`;
180
+ }
181
+ let ws = null;
182
+ try {
183
+ ws = new ws_1.default(`ws://${host}:${port}`, { headers });
184
+ }
185
+ catch {
186
+ return resolve('closed');
187
+ }
188
+ const cleanup = () => {
189
+ if (timer)
190
+ clearTimeout(timer);
191
+ if (ws) {
192
+ ws.removeAllListeners();
193
+ ws.on('error', () => { });
194
+ try {
195
+ if (ws.readyState === ws_1.default.OPEN) {
196
+ ws.close();
197
+ }
198
+ else {
199
+ ws.terminate();
200
+ }
201
+ }
202
+ catch { }
203
+ }
204
+ };
205
+ timer = setTimeout(() => {
206
+ if (!settled) {
207
+ settled = true;
208
+ cleanup();
209
+ resolve('closed');
210
+ }
211
+ }, timeoutMs);
212
+ ws.once('open', () => {
213
+ if (!settled) {
214
+ settled = true;
215
+ cleanup();
216
+ resolve('ready');
217
+ }
218
+ });
219
+ ws.once('error', (err) => {
220
+ if (!settled) {
221
+ settled = true;
222
+ const msg = err.message || '';
223
+ cleanup();
224
+ if (msg.includes('401') || msg.includes('Unexpected server response: 401')) {
225
+ resolve('unauthorized');
226
+ }
227
+ else {
228
+ resolve('closed');
229
+ }
230
+ }
231
+ });
232
+ });
233
+ }
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
+ /**
253
+ * Waits until the agent service has completed startup and successfully accepts WebSocket authentication.
254
+ */
255
+ async function waitForAgentReady(port = 8765, token, maxWaitMs = 45000, pollIntervalMs = 1000, onProgress) {
256
+ if (startupPromise) {
257
+ return startupPromise;
258
+ }
259
+ startupPromise = (async () => {
260
+ const startTime = Date.now();
261
+ try {
262
+ while (Date.now() - startTime < maxWaitMs) {
263
+ // If the spawned child process exited prematurely with an error code, abort
264
+ if (spawnedAgentProcess !== null && spawnedAgentProcess.exitCode !== null) {
265
+ return false;
266
+ }
267
+ const authState = await probeWebSocketAuth(port, token);
268
+ if (authState === 'ready') {
269
+ return true;
270
+ }
271
+ const elapsedSec = Math.floor((Date.now() - startTime) / 1000);
272
+ if (onProgress) {
273
+ onProgress(elapsedSec);
274
+ }
275
+ await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
276
+ }
277
+ return false;
278
+ }
279
+ finally {
280
+ startupPromise = null;
281
+ }
282
+ })();
283
+ return startupPromise;
284
+ }
285
+ async function spawnAgentDaemon(port = 8765) {
286
+ const token = getOrGenerateWsToken();
287
+ // 1. Check if the agent is already up and authenticated
288
+ const currentStatus = await probeWebSocketAuth(port, token, '127.0.0.1', 500);
289
+ if (currentStatus === 'ready') {
290
+ return { success: true };
291
+ }
292
+ // 2. If port is occupied by an unauthorized process, clean it up
293
+ if (currentStatus === 'unauthorized') {
294
+ killProcessOnPort(port);
295
+ await new Promise((resolve) => setTimeout(resolve, 500));
296
+ }
297
+ if (isAgentProcessRunning() || isAgentStarting()) {
298
+ return { success: true };
299
+ }
300
+ const scriptPath = findPythonAgentScript();
301
+ if (!scriptPath) {
302
+ return {
303
+ success: false,
304
+ error: 'No se encontró el script services/agent/main.py del agente Python.',
305
+ };
306
+ }
307
+ const agentDir = path_1.default.dirname(scriptPath);
308
+ const repoRoot = path_1.default.resolve(agentDir, '../..');
309
+ const pythonExec = findPythonExecutable(repoRoot);
310
+ try {
311
+ const child = (0, child_process_1.spawn)(pythonExec, ['main.py', '--ws-port', String(port), '--no-camera'], {
312
+ cwd: agentDir,
313
+ detached: true,
314
+ stdio: 'ignore', // run completely in background
315
+ windowsHide: true,
316
+ env: {
317
+ ...process.env,
318
+ HELIX_WS_TOKEN: token,
319
+ HELIX_DISABLE_CAMERA: '1',
320
+ CAMERA_ENABLED: 'false',
321
+ PYTHONUNBUFFERED: '1',
322
+ PYTHONIOENCODING: 'utf-8',
323
+ PYTHONPATH: repoRoot,
324
+ },
325
+ });
326
+ child.unref();
327
+ spawnedAgentProcess = child;
328
+ child.on('exit', () => {
329
+ spawnedAgentProcess = null;
330
+ });
331
+ return { success: true };
332
+ }
333
+ catch (err) {
334
+ return {
335
+ success: false,
336
+ error: `Error al invocar proceso Python: ${err.message}`,
337
+ };
338
+ }
339
+ }
@@ -0,0 +1,88 @@
1
+ export interface AgentMessage<T = unknown> {
2
+ type: string;
3
+ payload: T;
4
+ timestamp?: string;
5
+ request_id?: string;
6
+ error?: string;
7
+ }
8
+ export interface UserTextMessagePayload {
9
+ text: string;
10
+ conversation_id: string;
11
+ conversation_history: Array<{
12
+ role: 'user' | 'assistant';
13
+ content: string;
14
+ }>;
15
+ attachments?: Array<{
16
+ name: string;
17
+ mime_type: string;
18
+ size?: number;
19
+ data_base64: string;
20
+ }>;
21
+ }
22
+ export interface SkillSummary {
23
+ name: string;
24
+ description: string;
25
+ triggers?: string[];
26
+ instructions?: string;
27
+ custom?: boolean;
28
+ }
29
+ export interface McpToolSummary {
30
+ name: string;
31
+ description?: string;
32
+ parameters?: Record<string, unknown>;
33
+ }
34
+ export interface McpServerConfig {
35
+ name: string;
36
+ command: string;
37
+ args?: string[];
38
+ env?: Record<string, string>;
39
+ status?: 'connected' | 'idle' | 'connecting' | 'error';
40
+ error?: string;
41
+ tools?: McpToolSummary[];
42
+ }
43
+ export interface ProviderKeySlot {
44
+ slot: number;
45
+ health: 'healthy' | 'degraded' | 'cooldown' | 'unconfigured' | 'error';
46
+ configured: boolean;
47
+ masked?: string;
48
+ }
49
+ export interface ProviderSummary {
50
+ id: string;
51
+ name: string;
52
+ configured: boolean;
53
+ keys: ProviderKeySlot[];
54
+ models_available: number;
55
+ }
56
+ export interface ModelSummary {
57
+ id: string;
58
+ name?: string;
59
+ provider: string;
60
+ capabilities?: Record<string, boolean>;
61
+ }
62
+ export interface TaskSummary {
63
+ task_id: string;
64
+ prompt?: string;
65
+ status: 'PENDING' | 'RUNNING' | 'COMPLETED' | 'FAILED' | 'CANCELLED';
66
+ created_at?: string;
67
+ result?: string;
68
+ error?: string;
69
+ }
70
+ export interface HelixSettings {
71
+ voice_enabled?: boolean;
72
+ wake_word?: string;
73
+ mcp_servers?: McpServerConfig[];
74
+ default_model?: string | null;
75
+ preferred_provider?: string | null;
76
+ log_level?: string;
77
+ agent_ws_port?: number;
78
+ [key: string]: unknown;
79
+ }
80
+ export interface ConfirmationRequest {
81
+ id?: string;
82
+ request_id?: string;
83
+ action?: string;
84
+ description?: string;
85
+ risk_level?: 'LOW' | 'MEDIUM' | 'HIGH';
86
+ tool?: string;
87
+ arguments?: Record<string, unknown>;
88
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,3 @@
1
+ import type { AgentClient } from '../client/agent-client';
2
+ export declare function handleShowApiKey(client: AgentClient): Promise<void>;
3
+ export declare function handleConfigApiKey(client: AgentClient): Promise<void>;
@@ -0,0 +1,123 @@
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.handleShowApiKey = handleShowApiKey;
7
+ exports.handleConfigApiKey = handleConfigApiKey;
8
+ const prompts_1 = __importDefault(require("prompts"));
9
+ const chalk_1 = __importDefault(require("chalk"));
10
+ const colors_1 = require("../ui/colors");
11
+ const KNOWN_PROVIDERS = [
12
+ { title: 'OpenAI (GPT-4o, o1, o3-mini)', value: 'openai' },
13
+ { title: 'Anthropic (Claude 3.5 Sonnet, Claude 3.7)', value: 'anthropic' },
14
+ { title: 'Google (Gemini 2.0 Flash / Pro)', value: 'google' },
15
+ { title: 'Groq (Llama 3.3, DeepSeek R1)', value: 'groq' },
16
+ { title: 'Mistral AI (Mistral Large, Codestral)', value: 'mistral' },
17
+ { title: 'Ollama / Local Custom Provider', value: 'ollama' },
18
+ ];
19
+ async function handleShowApiKey(client) {
20
+ console.log(`\n${(0, colors_1.BOLD)('🔑 Configuración Actual de API Keys:')}\n`);
21
+ try {
22
+ const providers = await client.getProviders();
23
+ if (!providers || providers.length === 0) {
24
+ console.log((0, colors_1.GRAY)(' No se encontraron proveedores registrados en el agente.\n'));
25
+ return;
26
+ }
27
+ for (const provider of providers) {
28
+ const statusIcon = provider.configured ? (0, colors_1.GREEN)('●') : (0, colors_1.GRAY)('○');
29
+ const providerTitle = provider.configured
30
+ ? chalk_1.default.greenBright.bold(provider.name || provider.id)
31
+ : chalk_1.default.gray(provider.name || provider.id);
32
+ console.log(` ${statusIcon} ${providerTitle} ${(0, colors_1.DIM)(`(${provider.models_available || 0} modelos disponibles)`)}`);
33
+ if (provider.keys && provider.keys.length > 0) {
34
+ for (const slot of provider.keys) {
35
+ let healthBadge;
36
+ switch (slot.health) {
37
+ case 'healthy':
38
+ healthBadge = chalk_1.default.green('✔ Válida / Activa');
39
+ break;
40
+ case 'degraded':
41
+ healthBadge = chalk_1.default.yellow('⚠ Degradada');
42
+ break;
43
+ case 'cooldown':
44
+ healthBadge = chalk_1.default.red('⏳ Cooldown');
45
+ break;
46
+ case 'unconfigured':
47
+ healthBadge = chalk_1.default.gray('○ No configurada');
48
+ break;
49
+ default:
50
+ healthBadge = chalk_1.default.gray(slot.health);
51
+ }
52
+ const maskedText = slot.configured ? chalk_1.default.cyan('••••••••••••••••') : chalk_1.default.gray('vacío');
53
+ console.log(` ${(0, colors_1.GRAY)('Slot ' + slot.slot + ':')} ${maskedText} [${healthBadge}]`);
54
+ }
55
+ }
56
+ else {
57
+ console.log(` ${(0, colors_1.GRAY)('Sin slots de claves registrados')}`);
58
+ }
59
+ console.log('');
60
+ }
61
+ }
62
+ catch (error) {
63
+ console.log((0, colors_1.RED)(` Error al consultar API keys del agente: ${error.message}\n`));
64
+ }
65
+ }
66
+ async function handleConfigApiKey(client) {
67
+ console.log(`\n${(0, colors_1.BOLD)('⚙️ Configurador Interactivo de API Keys')}\n`);
68
+ // 1. Select Provider
69
+ const providerAnswer = await (0, prompts_1.default)({
70
+ type: 'select',
71
+ name: 'provider',
72
+ message: 'Selecciona el proveedor de IA para configurar:',
73
+ choices: KNOWN_PROVIDERS,
74
+ });
75
+ const providerId = providerAnswer.provider;
76
+ if (!providerId) {
77
+ console.log((0, colors_1.YELLOW)('Operación cancelada.\n'));
78
+ return;
79
+ }
80
+ // 2. Select Slot
81
+ const slotAnswer = await (0, prompts_1.default)({
82
+ type: 'select',
83
+ name: 'slot',
84
+ message: `Selecciona el Slot para ${providerId.toUpperCase()}:`,
85
+ choices: [
86
+ { title: 'Slot 1 (Principal)', value: 1 },
87
+ { title: 'Slot 2 (Respaldo)', value: 2 },
88
+ { title: 'Slot 3 (Rotación)', value: 3 },
89
+ { title: 'Añadir nuevo Slot', value: 4 },
90
+ ],
91
+ });
92
+ const slotNumber = slotAnswer.slot;
93
+ if (!slotNumber) {
94
+ console.log((0, colors_1.YELLOW)('Operación cancelada.\n'));
95
+ return;
96
+ }
97
+ // 3. Prompt for API Key
98
+ const keyAnswer = await (0, prompts_1.default)({
99
+ type: 'password',
100
+ name: 'apiKey',
101
+ message: `Introduce la API Key para ${providerId.toUpperCase()} (Slot ${slotNumber}):`,
102
+ validate: (val) => (val && val.trim().length > 0 ? true : 'La API Key no puede estar vacía'),
103
+ });
104
+ const apiKey = keyAnswer.apiKey?.trim();
105
+ if (!apiKey) {
106
+ console.log((0, colors_1.YELLOW)('Operación cancelada.\n'));
107
+ return;
108
+ }
109
+ console.log((0, colors_1.CYAN)(`\n Validando clave con ${providerId}...`));
110
+ try {
111
+ const health = await client.validateKey(providerId, slotNumber, apiKey);
112
+ if (health === 'healthy') {
113
+ console.log((0, colors_1.GREEN)(`\n ✔ ¡API Key validada y guardada con éxito en el almacén seguro!`));
114
+ console.log((0, colors_1.DIM)(` Proveedor: ${providerId} | Slot: ${slotNumber} | Estado: healthy\n`));
115
+ }
116
+ else {
117
+ console.log((0, colors_1.YELLOW)(`\n ⚠ La clave fue procesada pero su estado es: ${health}\n`));
118
+ }
119
+ }
120
+ catch (err) {
121
+ console.log((0, colors_1.RED)(`\n ✖ Error al validar la API Key: ${err.message}\n`));
122
+ }
123
+ }
@@ -0,0 +1,2 @@
1
+ import type { AgentClient } from '../client/agent-client';
2
+ export declare function handleBrowserCommand(client: AgentClient, args?: string): Promise<void>;
@@ -0,0 +1,35 @@
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.handleBrowserCommand = handleBrowserCommand;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const colors_1 = require("../ui/colors");
9
+ async function handleBrowserCommand(client, args) {
10
+ const parts = args ? args.trim().split(/\s+/) : [];
11
+ const subCommand = parts[0]?.toLowerCase();
12
+ const targetUrl = parts[1];
13
+ if (subCommand === 'open' && targetUrl) {
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()}`);
16
+ console.log((0, colors_1.GREEN)(` ✔ Comando enviado al agente.\n`));
17
+ return;
18
+ }
19
+ try {
20
+ const settings = await client.getSettings();
21
+ const engine = settings.browser_engine || 'chromium';
22
+ const headless = settings.browser_headless !== false;
23
+ console.log(`\n${(0, colors_1.BOLD)('🌐 Estado de Navegación y Browser Bridge:')}\n`);
24
+ console.log(` Motor: ${(0, colors_1.CYAN)(engine.toUpperCase())}`);
25
+ console.log(` Modo: ${headless ? chalk_1.default.yellow('Headless') : chalk_1.default.green('Ventana Visible')}`);
26
+ console.log(` Descargas: ${settings.block_downloads ? (0, colors_1.RED)('Bloqueadas') : (0, colors_1.GREEN)('Permitidas')}`);
27
+ console.log(` Dominios: ${settings.block_untrusted_domains ? (0, colors_1.YELLOW)('Filtrado Estricto') : (0, colors_1.GRAY)('Abierto')}`);
28
+ console.log(`\n ${(0, colors_1.DIM)('Comandos disponibles:')}`);
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`);
31
+ }
32
+ catch (err) {
33
+ console.log((0, colors_1.RED)(` Error al consultar estado del navegador: ${err.message}\n`));
34
+ }
35
+ }
@@ -0,0 +1,3 @@
1
+ import type { AgentClient } from '../client/agent-client';
2
+ export declare function handleListMcp(client: AgentClient): Promise<void>;
3
+ export declare function handleCreateMcp(client: AgentClient): Promise<void>;