chocolatito-code 1.6.6 → 1.6.8
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 +437 -341
- package/dist/agent/context.d.ts +6 -0
- package/dist/agent/context.js +69 -2
- package/dist/agent/loop.js +8 -0
- package/dist/agent/toolGate.js +13 -7
- package/dist/agent/verifier.js +2 -1
- package/dist/config/permissions.d.ts +17 -0
- package/dist/config/permissions.js +47 -7
- package/dist/config/plataforma.d.ts +62 -0
- package/dist/config/plataforma.js +109 -0
- package/dist/config/updater.js +4 -1
- package/dist/hooks/manager.js +2 -1
- package/dist/index.js +164 -39
- package/dist/memory/manager.d.ts +15 -5
- package/dist/memory/manager.js +146 -29
- package/dist/servidor/captura.d.ts +36 -0
- package/dist/servidor/captura.js +74 -0
- package/dist/servidor/pagina.d.ts +27 -0
- package/dist/servidor/pagina.js +268 -0
- package/dist/servidor/puente.d.ts +34 -0
- package/dist/servidor/puente.js +115 -0
- package/dist/servidor/servidor.d.ts +61 -0
- package/dist/servidor/servidor.js +249 -0
- package/dist/sessions/manager.d.ts +8 -0
- package/dist/sessions/manager.js +44 -0
- package/dist/sessions/resume.d.ts +12 -0
- package/dist/sessions/resume.js +10 -0
- package/dist/tools/backgroundTask.d.ts +64 -0
- package/dist/tools/backgroundTask.js +264 -0
- package/dist/tools/browserExtension.d.ts +2 -1
- package/dist/tools/browserExtension.js +48 -0
- package/dist/tools/computerUse.js +51 -9
- package/dist/tools/definitions.js +93 -1
- package/dist/tools/gitAudit.d.ts +41 -0
- package/dist/tools/gitAudit.js +282 -0
- package/dist/tools/runCommand.js +5 -2
- package/dist/tools/runner.js +36 -2
- package/dist/tools/safety.d.ts +1 -0
- package/dist/tools/safety.js +3 -0
- package/dist/tools/todoTool.d.ts +2 -0
- package/dist/tools/todoTool.js +29 -0
- package/dist/tools/toolDefsComputer.js +9 -0
- package/dist/tools/win/hostScript.js +7 -2
- package/dist/ui/comandos.js +2 -0
- package/dist/ui/ink/App.d.ts +0 -23
- package/dist/ui/ink/App.js +86 -57
- package/dist/ui/ink/Prompt.d.ts +16 -6
- package/dist/ui/ink/Prompt.js +37 -62
- package/dist/ui/ink/montarApp.d.ts +34 -0
- package/dist/ui/ink/montarApp.js +93 -1
- package/dist/ui/ink/prestamo.d.ts +63 -0
- package/dist/ui/ink/prestamo.js +92 -0
- package/dist/ui/ink/teclado.d.ts +48 -0
- package/dist/ui/ink/teclado.js +96 -0
- package/dist/ui/ink/transcripcion.d.ts +114 -0
- package/dist/ui/ink/transcripcion.js +180 -0
- package/dist/ui/interrupt.js +4 -1
- package/dist/ui/loopPrompt.d.ts +6 -7
- package/dist/ui/loopPrompt.js +14 -1
- package/dist/ui/marco.d.ts +3 -0
- package/dist/ui/marco.js +22 -3
- package/dist/ui/pantalla.d.ts +45 -17
- package/dist/ui/pantalla.js +150 -92
- package/dist/ui/permissionPrompt.d.ts +19 -17
- package/dist/ui/permissionPrompt.js +44 -1
- package/dist/ui/pieFijo.d.ts +1 -1
- package/dist/ui/pieFijo.js +27 -5
- package/dist/ui/renderer.js +8 -0
- package/dist/ui/selector.d.ts +6 -4
- package/dist/ui/selector.js +14 -1
- package/extension/background.js +80 -0
- package/extension/content.js +106 -0
- package/extension/manifest.json +3 -2
- package/package.json +3 -2
- package/dist/ui/historial.d.ts +0 -49
- package/dist/ui/historial.js +0 -92
|
@@ -799,6 +799,54 @@ export async function chromeExtension(params, cwd = process.cwd(), apiKey) {
|
|
|
799
799
|
? "Trabajo cerrado: pestana desagrupada y aviso retirado."
|
|
800
800
|
: fail(action, res.error);
|
|
801
801
|
}
|
|
802
|
+
case "console_logs": {
|
|
803
|
+
const res = await extensionBridge.send("getConsoleLogs", {
|
|
804
|
+
tabId: params.tabId,
|
|
805
|
+
clear: params.clear === true,
|
|
806
|
+
});
|
|
807
|
+
if (!res.ok)
|
|
808
|
+
return fail(action, res.error);
|
|
809
|
+
const logs = res.data?.logs || [];
|
|
810
|
+
if (logs.length === 0) {
|
|
811
|
+
return "Sin errores de consola registrados en la página.";
|
|
812
|
+
}
|
|
813
|
+
const formateados = logs
|
|
814
|
+
.map((log) => {
|
|
815
|
+
const hora = log.timestamp ? new Date(log.timestamp).toLocaleTimeString() : "";
|
|
816
|
+
const nivel = (log.level || "error").toUpperCase();
|
|
817
|
+
const prefijo = hora ? `[${hora}] [${nivel}]` : `[${nivel}]`;
|
|
818
|
+
let linea = `${prefijo} ${log.message || ""}`;
|
|
819
|
+
if (log.url)
|
|
820
|
+
linea += ` (${log.url})`;
|
|
821
|
+
if (log.stack)
|
|
822
|
+
linea += `\n Stack: ${log.stack.split("\n").slice(0, 3).join("\n ")}`;
|
|
823
|
+
return linea;
|
|
824
|
+
})
|
|
825
|
+
.join("\n");
|
|
826
|
+
return `LOGS DE CONSOLA (${logs.length} registro${logs.length === 1 ? "" : "s"}):\n${formateados}${params.clear ? "\n(Buffer limpiado)" : ""}`;
|
|
827
|
+
}
|
|
828
|
+
case "network_errors": {
|
|
829
|
+
const res = await extensionBridge.send("getNetworkErrors", {
|
|
830
|
+
tabId: params.tabId,
|
|
831
|
+
clear: params.clear === true,
|
|
832
|
+
});
|
|
833
|
+
if (!res.ok)
|
|
834
|
+
return fail(action, res.error);
|
|
835
|
+
const errors = res.data?.errors || [];
|
|
836
|
+
if (errors.length === 0) {
|
|
837
|
+
return "Sin errores de red registrados en la página.";
|
|
838
|
+
}
|
|
839
|
+
const formateados = errors
|
|
840
|
+
.map((err) => {
|
|
841
|
+
const hora = err.timestamp ? new Date(err.timestamp).toLocaleTimeString() : "";
|
|
842
|
+
const status = err.status ? ` HTTP ${err.status}` : "";
|
|
843
|
+
const detalle = err.error && err.error !== `HTTP ${err.status}` ? ` - ${err.error}` : "";
|
|
844
|
+
const horaStr = hora ? `[${hora}] ` : "";
|
|
845
|
+
return `${horaStr}${err.method || "GET"}${status} -> ${err.url}${detalle}`;
|
|
846
|
+
})
|
|
847
|
+
.join("\n");
|
|
848
|
+
return `ERRORES DE RED (${errors.length} peticion${errors.length === 1 ? "" : "es"} fallida${errors.length === 1 ? "" : "s"}):\n${formateados}${params.clear ? "\n(Buffer limpiado)" : ""}`;
|
|
849
|
+
}
|
|
802
850
|
default:
|
|
803
851
|
return fail(String(action), "accion no reconocida.");
|
|
804
852
|
}
|
|
@@ -220,10 +220,31 @@ async function estadoTrasActuar(args) {
|
|
|
220
220
|
export async function computerUse(params, cwd = process.cwd(), apiKey) {
|
|
221
221
|
const { action } = params;
|
|
222
222
|
const mode = params.mode || "background";
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
223
|
+
let targetX = params.coordinate ? params.coordinate[0] : params.x;
|
|
224
|
+
let targetY = params.coordinate ? params.coordinate[1] : params.y;
|
|
225
|
+
let startX = params.start_coordinate ? params.start_coordinate[0] : params.x;
|
|
226
|
+
let startY = params.start_coordinate ? params.start_coordinate[1] : params.y;
|
|
227
|
+
// Soporte de coordenadas normalizadas (0.0 a 1.0) estilo Astra / OpenAI Operator
|
|
228
|
+
if (targetX !== undefined && targetY !== undefined && targetX > 0 && targetX <= 1.0 && targetY > 0 && targetY <= 1.0) {
|
|
229
|
+
try {
|
|
230
|
+
const sInfo = await winHost.send("screen_info");
|
|
231
|
+
const sw = sInfo.virtualWidth || sInfo.primaryWidth || 1920;
|
|
232
|
+
const sh = sInfo.virtualHeight || sInfo.primaryHeight || 1080;
|
|
233
|
+
targetX = Math.round(targetX * sw);
|
|
234
|
+
targetY = Math.round(targetY * sh);
|
|
235
|
+
}
|
|
236
|
+
catch { }
|
|
237
|
+
}
|
|
238
|
+
if (startX !== undefined && startY !== undefined && startX > 0 && startX <= 1.0 && startY > 0 && startY <= 1.0) {
|
|
239
|
+
try {
|
|
240
|
+
const sInfo = await winHost.send("screen_info");
|
|
241
|
+
const sw = sInfo.virtualWidth || sInfo.primaryWidth || 1920;
|
|
242
|
+
const sh = sInfo.virtualHeight || sInfo.primaryHeight || 1080;
|
|
243
|
+
startX = Math.round(startX * sw);
|
|
244
|
+
startY = Math.round(startY * sh);
|
|
245
|
+
}
|
|
246
|
+
catch { }
|
|
247
|
+
}
|
|
227
248
|
try {
|
|
228
249
|
switch (action) {
|
|
229
250
|
// ---------------------------------------------------------------- espera
|
|
@@ -459,7 +480,7 @@ export async function computerUse(params, cwd = process.cwd(), apiKey) {
|
|
|
459
480
|
if (!apiKey) {
|
|
460
481
|
return `Sin coincidencias en el arbol de accesibilidad para "${query}", y no hay API key para recurrir a la vision.`;
|
|
461
482
|
}
|
|
462
|
-
const shotPath = path.join(ensureCache(), "find_target.
|
|
483
|
+
const shotPath = path.join(ensureCache(), "find_target.jpg");
|
|
463
484
|
const shot = await captureFor(params, shotPath);
|
|
464
485
|
if (!shot.ok)
|
|
465
486
|
return fail(action, shot.error || "no se pudo capturar");
|
|
@@ -475,7 +496,7 @@ export async function computerUse(params, cwd = process.cwd(), apiKey) {
|
|
|
475
496
|
}
|
|
476
497
|
// ---------------------------------------------------------------- captura
|
|
477
498
|
case "screenshot": {
|
|
478
|
-
const out = resolveOut(params.outputPath, "screen_latest.
|
|
499
|
+
const out = resolveOut(params.outputPath, "screen_latest.jpg", cwd);
|
|
479
500
|
const choice = await resolveWindow(params);
|
|
480
501
|
if (!choice.ok)
|
|
481
502
|
return fail(action, choice.error);
|
|
@@ -502,6 +523,7 @@ export async function computerUse(params, cwd = process.cwd(), apiKey) {
|
|
|
502
523
|
// ------------------------------------------------------------ raton/teclado
|
|
503
524
|
case "left_click":
|
|
504
525
|
case "click":
|
|
526
|
+
case "mouse_click":
|
|
505
527
|
case "double_click":
|
|
506
528
|
case "triple_click":
|
|
507
529
|
case "right_click":
|
|
@@ -547,7 +569,8 @@ export async function computerUse(params, cwd = process.cwd(), apiKey) {
|
|
|
547
569
|
return fail(action, res.error || "fallo desconocido");
|
|
548
570
|
return `Cursor movido a (${targetX}, ${targetY}).`;
|
|
549
571
|
}
|
|
550
|
-
case "left_click_drag":
|
|
572
|
+
case "left_click_drag":
|
|
573
|
+
case "drag": {
|
|
551
574
|
if (targetX === undefined || targetY === undefined || startX === undefined || startY === undefined) {
|
|
552
575
|
return fail(action, "faltan start_coordinate y coordinate.");
|
|
553
576
|
}
|
|
@@ -589,7 +612,9 @@ export async function computerUse(params, cwd = process.cwd(), apiKey) {
|
|
|
589
612
|
return `Escrito en la ventana enfocada (${res.chars} caracteres): "${preview(params.text)}"`;
|
|
590
613
|
}
|
|
591
614
|
case "key":
|
|
592
|
-
case "hotkey":
|
|
615
|
+
case "hotkey":
|
|
616
|
+
case "press":
|
|
617
|
+
case "key_press": {
|
|
593
618
|
const combo = normalizeKeys(params.keys || params.key || params.text || "");
|
|
594
619
|
if (!combo)
|
|
595
620
|
return fail(action, 'indica la tecla en "keys" (ej. "Return", "ctrl+t", "alt+Tab").');
|
|
@@ -725,7 +750,7 @@ function normalizeKeys(raw) {
|
|
|
725
750
|
let k = raw.trim();
|
|
726
751
|
if (!k)
|
|
727
752
|
return "";
|
|
728
|
-
// Restos del formato SendKeys
|
|
753
|
+
// Restos del formato SendKeys y alias comunes de Astra/Operator/Playwright
|
|
729
754
|
const sendKeysMap = {
|
|
730
755
|
"{ENTER}": "Return",
|
|
731
756
|
"{ESC}": "Escape",
|
|
@@ -740,7 +765,23 @@ function normalizeKeys(raw) {
|
|
|
740
765
|
"^{TAB}": "ctrl+Tab",
|
|
741
766
|
"%{TAB}": "alt+Tab",
|
|
742
767
|
"%{F4}": "alt+F4",
|
|
768
|
+
enter: "Return",
|
|
769
|
+
return: "Return",
|
|
770
|
+
esc: "Escape",
|
|
771
|
+
escape: "Escape",
|
|
772
|
+
tab: "Tab",
|
|
773
|
+
space: "Space",
|
|
774
|
+
spacebar: "Space",
|
|
775
|
+
backspace: "BackSpace",
|
|
776
|
+
delete: "Delete",
|
|
777
|
+
del: "Delete",
|
|
778
|
+
up: "Up",
|
|
779
|
+
down: "Down",
|
|
780
|
+
left: "Left",
|
|
781
|
+
right: "Right",
|
|
743
782
|
};
|
|
783
|
+
if (sendKeysMap[k.toLowerCase()])
|
|
784
|
+
return sendKeysMap[k.toLowerCase()];
|
|
744
785
|
if (sendKeysMap[k])
|
|
745
786
|
return sendKeysMap[k];
|
|
746
787
|
if (/^\^\{?[A-Za-z0-9]+\}?$/.test(k))
|
|
@@ -749,6 +790,7 @@ function normalizeKeys(raw) {
|
|
|
749
790
|
k = "alt+" + k.replace(/[%{}]/g, "");
|
|
750
791
|
else if (/^\{[A-Za-z0-9]+\}$/.test(k))
|
|
751
792
|
k = k.slice(1, -1);
|
|
793
|
+
k = k.replace(/control\+/gi, "ctrl+").replace(/super\+/gi, "win+").replace(/cmd\+/gi, "win+").replace(/meta\+/gi, "win+");
|
|
752
794
|
return k.replace(/\s+/g, "");
|
|
753
795
|
}
|
|
754
796
|
function preview(text) {
|
|
@@ -54,7 +54,7 @@ export const TOOLS = [
|
|
|
54
54
|
type: "function",
|
|
55
55
|
function: {
|
|
56
56
|
name: "save_memory",
|
|
57
|
-
description: "Guarda un hecho importante, preferencia del usuario o aprendizaje en la memoria persistente (~/.chocolatito/memory/).",
|
|
57
|
+
description: "Guarda un hecho importante, preferencia del usuario o aprendizaje en la memoria persistente (~/.chocolatito/memory/ o local del proyecto).",
|
|
58
58
|
parameters: {
|
|
59
59
|
type: "object",
|
|
60
60
|
properties: {
|
|
@@ -66,6 +66,11 @@ export const TOOLS = [
|
|
|
66
66
|
type: "string",
|
|
67
67
|
description: "El hecho o conocimiento a recordar en futuras sesiones.",
|
|
68
68
|
},
|
|
69
|
+
scope: {
|
|
70
|
+
type: "string",
|
|
71
|
+
enum: ["project", "global"],
|
|
72
|
+
description: "Ámbito donde guardar: 'project' (local del proyecto en .chocolatito/memory/) o 'global' (en ~/.chocolatito/memory/). Por defecto 'project' si se está en un proyecto válido.",
|
|
73
|
+
},
|
|
69
74
|
},
|
|
70
75
|
required: ["topic", "fact"],
|
|
71
76
|
},
|
|
@@ -83,6 +88,11 @@ export const TOOLS = [
|
|
|
83
88
|
type: "string",
|
|
84
89
|
description: "El tema o nombre del archivo de memoria a consultar.",
|
|
85
90
|
},
|
|
91
|
+
scope: {
|
|
92
|
+
type: "string",
|
|
93
|
+
enum: ["project", "global"],
|
|
94
|
+
description: "Ámbito de consulta: 'project' (local del proyecto) o 'global' (en ~/.chocolatito/memory/). Si no se especifica, busca en proyecto primero y luego en global.",
|
|
95
|
+
},
|
|
86
96
|
},
|
|
87
97
|
required: ["topic"],
|
|
88
98
|
},
|
|
@@ -299,6 +309,88 @@ export const TOOLS = [
|
|
|
299
309
|
},
|
|
300
310
|
},
|
|
301
311
|
},
|
|
312
|
+
{
|
|
313
|
+
type: "function",
|
|
314
|
+
function: {
|
|
315
|
+
name: "start_background_task",
|
|
316
|
+
description: "Inicia un comando en segundo plano sin bloquear el bucle de interacción (útil para servidores dev, watchers, compilaciones continuas).",
|
|
317
|
+
parameters: {
|
|
318
|
+
type: "object",
|
|
319
|
+
properties: {
|
|
320
|
+
command: {
|
|
321
|
+
type: "string",
|
|
322
|
+
description: "El comando a ejecutar en segundo plano.",
|
|
323
|
+
},
|
|
324
|
+
description: {
|
|
325
|
+
type: "string",
|
|
326
|
+
description: "Descripción opcional de la tarea.",
|
|
327
|
+
},
|
|
328
|
+
cwd: {
|
|
329
|
+
type: "string",
|
|
330
|
+
description: "Directorio de trabajo desde donde ejecutar la tarea (opcional).",
|
|
331
|
+
},
|
|
332
|
+
},
|
|
333
|
+
required: ["command"],
|
|
334
|
+
},
|
|
335
|
+
},
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
type: "function",
|
|
339
|
+
function: {
|
|
340
|
+
name: "read_task_output",
|
|
341
|
+
description: "Lee la salida acumulada (logs) de una tarea en segundo plano sin bloquear.",
|
|
342
|
+
parameters: {
|
|
343
|
+
type: "object",
|
|
344
|
+
properties: {
|
|
345
|
+
taskId: {
|
|
346
|
+
type: "string",
|
|
347
|
+
description: "El ID de la tarea a consultar.",
|
|
348
|
+
},
|
|
349
|
+
lines: {
|
|
350
|
+
type: "integer",
|
|
351
|
+
description: "Número de líneas a devolver (por defecto 100).",
|
|
352
|
+
},
|
|
353
|
+
offset: {
|
|
354
|
+
type: "integer",
|
|
355
|
+
description: "Línea inicial desde donde comenzar a leer (opcional).",
|
|
356
|
+
},
|
|
357
|
+
},
|
|
358
|
+
required: ["taskId"],
|
|
359
|
+
},
|
|
360
|
+
},
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
type: "function",
|
|
364
|
+
function: {
|
|
365
|
+
name: "list_background_tasks",
|
|
366
|
+
description: "Lista todas las tareas en segundo plano registradas con su estado, PID y tiempo transcurrido.",
|
|
367
|
+
parameters: {
|
|
368
|
+
type: "object",
|
|
369
|
+
properties: {},
|
|
370
|
+
},
|
|
371
|
+
},
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
type: "function",
|
|
375
|
+
function: {
|
|
376
|
+
name: "stop_background_task",
|
|
377
|
+
description: "Detiene una tarea en segundo plano activa y termina todo su árbol de procesos.",
|
|
378
|
+
parameters: {
|
|
379
|
+
type: "object",
|
|
380
|
+
properties: {
|
|
381
|
+
taskId: {
|
|
382
|
+
type: "string",
|
|
383
|
+
description: "El ID de la tarea a detener.",
|
|
384
|
+
},
|
|
385
|
+
force: {
|
|
386
|
+
type: "boolean",
|
|
387
|
+
description: "Si es true, fuerza la terminación inmediata del proceso (por defecto true).",
|
|
388
|
+
},
|
|
389
|
+
},
|
|
390
|
+
required: ["taskId"],
|
|
391
|
+
},
|
|
392
|
+
},
|
|
393
|
+
},
|
|
302
394
|
{
|
|
303
395
|
type: "function",
|
|
304
396
|
function: {
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export interface AuditFinding {
|
|
2
|
+
type: "secret" | "debug" | "sensitive_file";
|
|
3
|
+
file: string;
|
|
4
|
+
line?: number;
|
|
5
|
+
message: string;
|
|
6
|
+
snippet?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface AuditStats {
|
|
9
|
+
filesChanged: number;
|
|
10
|
+
insertions: number;
|
|
11
|
+
additions: number;
|
|
12
|
+
deletions: number;
|
|
13
|
+
files: string[];
|
|
14
|
+
}
|
|
15
|
+
export interface AuditReport {
|
|
16
|
+
ok: boolean;
|
|
17
|
+
clean: boolean;
|
|
18
|
+
stats: AuditStats;
|
|
19
|
+
secrets: AuditFinding[];
|
|
20
|
+
debugLogs: AuditFinding[];
|
|
21
|
+
residualLogs: AuditFinding[];
|
|
22
|
+
sensitiveFiles: string[];
|
|
23
|
+
suggestedCommit: string;
|
|
24
|
+
rawDiff: string;
|
|
25
|
+
}
|
|
26
|
+
export declare const SECRET_PATTERNS: Array<{
|
|
27
|
+
name: string;
|
|
28
|
+
pattern: RegExp;
|
|
29
|
+
}>;
|
|
30
|
+
export declare const SENSITIVE_FILE_PATTERN: RegExp;
|
|
31
|
+
export declare const DEBUG_PATTERNS: Array<{
|
|
32
|
+
name: string;
|
|
33
|
+
pattern: RegExp;
|
|
34
|
+
}>;
|
|
35
|
+
export declare function esDocumentacion(archivo: string): boolean;
|
|
36
|
+
/** Quita los trozos entre comillas invertidas: `asi` y ``asi``. */
|
|
37
|
+
export declare function sinCitasEnLinea(texto: string): string;
|
|
38
|
+
export declare function auditDiffText(rawDiff: string, changedFiles?: string[]): AuditReport;
|
|
39
|
+
export declare function auditGitDiff(cwd?: string, mockDiff?: string): Promise<AuditReport>;
|
|
40
|
+
export declare function suggestConventionalCommit(files: string[], _insertions: number, _deletions: number): string;
|
|
41
|
+
export declare function formatAuditReport(report: AuditReport): string;
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { ejecutarGit } from "./gitTools.js";
|
|
3
|
+
export const SECRET_PATTERNS = [
|
|
4
|
+
{ name: "Clave de API de OpenAI", pattern: /\bsk-[a-zA-Z0-9]{20,}\b/ },
|
|
5
|
+
{ name: "Clave de API de Anthropic", pattern: /\bsk-ant-[a-zA-Z0-9_-]{20,}\b/ },
|
|
6
|
+
{ name: "Clave de AWS (Access Key)", pattern: /\bAKIA[0-9A-Z]{16}\b/ },
|
|
7
|
+
{ name: "Clave de API de Google", pattern: /\bAIza[0-9A-Za-z\-_]{30,40}\b/ },
|
|
8
|
+
{ name: "Token de acceso de GitHub", pattern: /\bgh[pousr]_[A-Za-z0-9_]{36,}\b/ },
|
|
9
|
+
{ name: "Clave privada (RSA/EC/OpenSSH)", pattern: /-----BEGIN (?:[A-Z ]+)?PRIVATE KEY-----/ },
|
|
10
|
+
{
|
|
11
|
+
name: "Asignación genérica de credencial o token",
|
|
12
|
+
pattern: /(?:secret|password|passwd|api_key|apikey|token|auth_token)\s*[:=]\s*["'][^"'\s]{8,}["']/i,
|
|
13
|
+
},
|
|
14
|
+
];
|
|
15
|
+
export const SENSITIVE_FILE_PATTERN = /(?:^|[/\\])(\.env(?:\.[a-zA-Z0-9_-]+)?|id_rsa|id_ed25519|.*\.pem)$/i;
|
|
16
|
+
export const DEBUG_PATTERNS = [
|
|
17
|
+
{ name: "console.log", pattern: /\bconsole\.log\s*\(/ },
|
|
18
|
+
{ name: "console.debug", pattern: /\bconsole\.debug\s*\(/ },
|
|
19
|
+
{ name: "debugger", pattern: /\bdebugger\b/ },
|
|
20
|
+
{ name: "print()", pattern: /(?<![\w.])print\s*\(/ },
|
|
21
|
+
{ name: "var_dump", pattern: /\bvar_dump\s*\(/ },
|
|
22
|
+
{ name: "dd()", pattern: /(?<![\w.])dd\s*\(/ },
|
|
23
|
+
];
|
|
24
|
+
/**
|
|
25
|
+
* LA DOCUMENTACION NO ES CODIGO, Y ESO AQUI IMPORTA
|
|
26
|
+
*
|
|
27
|
+
* La primera vez que se paso este auditor por su propio repositorio, se denuncio
|
|
28
|
+
* a si mismo: la linea del README que dice que detecta `console.log`, `debugger`
|
|
29
|
+
* y `print()` contiene, por fuerza, un `console.log`, un `debugger` y un
|
|
30
|
+
* `print()`. Cinco avisos de calidad y una "clave privada" que era la cabecera
|
|
31
|
+
* `-----BEGIN PRIVATE KEY-----` citada dentro de un parrafo.
|
|
32
|
+
*
|
|
33
|
+
* Un auditor que grita en cuanto alguien lo documenta es un auditor que se deja
|
|
34
|
+
* de leer, y entonces no sirve para lo unico que tiene que servir: que el aviso
|
|
35
|
+
* de verdad se vea. Asi que:
|
|
36
|
+
*
|
|
37
|
+
* - Las sentencias de depuracion solo se buscan en archivos de codigo. Un
|
|
38
|
+
* `console.log` dentro de un .md no es un resto de depuracion: es una frase.
|
|
39
|
+
* - En documentacion, lo que va entre comillas invertidas es una cita, no una
|
|
40
|
+
* credencial. Se quitan esos trozos ANTES de buscar secretos.
|
|
41
|
+
*
|
|
42
|
+
* Lo que NO se toca: una clave de verdad pegada en un README sigue saltando. Una
|
|
43
|
+
* clave real ocupa mil y pico caracteres y llega en un bloque cercado o a pelo,
|
|
44
|
+
* nunca dentro de un `span` de una linea, que es lo unico que se ignora.
|
|
45
|
+
*/
|
|
46
|
+
const EXTENSIONES_DE_DOCUMENTACION = new Set([
|
|
47
|
+
"md",
|
|
48
|
+
"markdown",
|
|
49
|
+
"mdx",
|
|
50
|
+
"txt",
|
|
51
|
+
"rst",
|
|
52
|
+
"adoc",
|
|
53
|
+
"org",
|
|
54
|
+
]);
|
|
55
|
+
export function esDocumentacion(archivo) {
|
|
56
|
+
const ext = archivo.split(".").pop()?.toLowerCase() || "";
|
|
57
|
+
return EXTENSIONES_DE_DOCUMENTACION.has(ext);
|
|
58
|
+
}
|
|
59
|
+
/** Quita los trozos entre comillas invertidas: `asi` y ``asi``. */
|
|
60
|
+
export function sinCitasEnLinea(texto) {
|
|
61
|
+
return texto.replace(/``[^`]*``/g, " ").replace(/`[^`]*`/g, " ");
|
|
62
|
+
}
|
|
63
|
+
export function auditDiffText(rawDiff, changedFiles = []) {
|
|
64
|
+
const secrets = [];
|
|
65
|
+
const debugLogs = [];
|
|
66
|
+
const sensitiveFiles = [];
|
|
67
|
+
for (const f of changedFiles) {
|
|
68
|
+
if (SENSITIVE_FILE_PATTERN.test(f) && !sensitiveFiles.includes(f)) {
|
|
69
|
+
sensitiveFiles.push(f);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
let currentFile = "";
|
|
73
|
+
let currentLine = 0;
|
|
74
|
+
let insertions = 0;
|
|
75
|
+
let deletions = 0;
|
|
76
|
+
const diffFiles = new Set(changedFiles);
|
|
77
|
+
const lines = rawDiff.split("\n");
|
|
78
|
+
for (const line of lines) {
|
|
79
|
+
if (line.startsWith("diff --git ")) {
|
|
80
|
+
const parts = line.split(" ");
|
|
81
|
+
currentFile = parts[parts.length - 1]?.replace(/^b\//, "") || "";
|
|
82
|
+
if (currentFile)
|
|
83
|
+
diffFiles.add(currentFile);
|
|
84
|
+
if (SENSITIVE_FILE_PATTERN.test(currentFile) && !sensitiveFiles.includes(currentFile)) {
|
|
85
|
+
sensitiveFiles.push(currentFile);
|
|
86
|
+
}
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (line.startsWith("+++ b/")) {
|
|
90
|
+
currentFile = line.slice(6).trim();
|
|
91
|
+
if (currentFile)
|
|
92
|
+
diffFiles.add(currentFile);
|
|
93
|
+
if (SENSITIVE_FILE_PATTERN.test(currentFile) && !sensitiveFiles.includes(currentFile)) {
|
|
94
|
+
sensitiveFiles.push(currentFile);
|
|
95
|
+
}
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (line.startsWith("@@ ")) {
|
|
99
|
+
const hunkMatch = line.match(/@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@/);
|
|
100
|
+
if (hunkMatch) {
|
|
101
|
+
currentLine = parseInt(hunkMatch[1], 10) - 1;
|
|
102
|
+
}
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (line.startsWith("+") && !line.startsWith("+++")) {
|
|
106
|
+
insertions++;
|
|
107
|
+
currentLine++;
|
|
108
|
+
const addedContent = line.slice(1).trim();
|
|
109
|
+
const enDocumentacion = esDocumentacion(currentFile || "");
|
|
110
|
+
const paraSecretos = enDocumentacion ? sinCitasEnLinea(addedContent) : addedContent;
|
|
111
|
+
for (const sp of SECRET_PATTERNS) {
|
|
112
|
+
if (sp.pattern.test(paraSecretos)) {
|
|
113
|
+
secrets.push({
|
|
114
|
+
type: "secret",
|
|
115
|
+
file: currentFile || "diff",
|
|
116
|
+
line: currentLine,
|
|
117
|
+
message: sp.name,
|
|
118
|
+
snippet: addedContent.length > 50 ? `${addedContent.slice(0, 47)}…` : addedContent,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
for (const dp of DEBUG_PATTERNS) {
|
|
123
|
+
if (enDocumentacion)
|
|
124
|
+
break;
|
|
125
|
+
if (dp.pattern.test(addedContent)) {
|
|
126
|
+
debugLogs.push({
|
|
127
|
+
type: "debug",
|
|
128
|
+
file: currentFile || "diff",
|
|
129
|
+
line: currentLine,
|
|
130
|
+
message: dp.name,
|
|
131
|
+
snippet: addedContent.length > 50 ? `${addedContent.slice(0, 47)}…` : addedContent,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
else if (line.startsWith("-") && !line.startsWith("---")) {
|
|
137
|
+
deletions++;
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
currentLine++;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
const allFiles = Array.from(diffFiles);
|
|
144
|
+
const clean = allFiles.length === 0 && insertions === 0 && deletions === 0 && secrets.length === 0 && sensitiveFiles.length === 0;
|
|
145
|
+
const suggestedCommit = suggestConventionalCommit(allFiles, insertions, deletions);
|
|
146
|
+
return {
|
|
147
|
+
ok: true,
|
|
148
|
+
clean,
|
|
149
|
+
stats: {
|
|
150
|
+
filesChanged: allFiles.length,
|
|
151
|
+
insertions,
|
|
152
|
+
additions: insertions,
|
|
153
|
+
deletions,
|
|
154
|
+
files: allFiles,
|
|
155
|
+
},
|
|
156
|
+
secrets,
|
|
157
|
+
debugLogs,
|
|
158
|
+
residualLogs: debugLogs,
|
|
159
|
+
sensitiveFiles,
|
|
160
|
+
suggestedCommit,
|
|
161
|
+
rawDiff,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
export async function auditGitDiff(cwd = process.cwd(), mockDiff) {
|
|
165
|
+
if (mockDiff !== undefined) {
|
|
166
|
+
return auditDiffText(mockDiff);
|
|
167
|
+
}
|
|
168
|
+
const statusRes = await ejecutarGit(["status", "--porcelain"], cwd);
|
|
169
|
+
if (!statusRes.ok) {
|
|
170
|
+
return {
|
|
171
|
+
ok: false,
|
|
172
|
+
clean: true,
|
|
173
|
+
stats: { filesChanged: 0, insertions: 0, additions: 0, deletions: 0, files: [] },
|
|
174
|
+
secrets: [],
|
|
175
|
+
debugLogs: [],
|
|
176
|
+
residualLogs: [],
|
|
177
|
+
sensitiveFiles: [],
|
|
178
|
+
suggestedCommit: "chore: update project",
|
|
179
|
+
rawDiff: statusRes.error,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
const statusLines = statusRes.stdout.split("\n").filter((l) => l.trim().length > 0);
|
|
183
|
+
if (statusLines.length === 0) {
|
|
184
|
+
return {
|
|
185
|
+
ok: true,
|
|
186
|
+
clean: true,
|
|
187
|
+
stats: { filesChanged: 0, insertions: 0, additions: 0, deletions: 0, files: [] },
|
|
188
|
+
secrets: [],
|
|
189
|
+
debugLogs: [],
|
|
190
|
+
residualLogs: [],
|
|
191
|
+
sensitiveFiles: [],
|
|
192
|
+
suggestedCommit: "chore: no pending changes",
|
|
193
|
+
rawDiff: "",
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
const changedFiles = [];
|
|
197
|
+
for (const sl of statusLines) {
|
|
198
|
+
const filePath = sl.slice(3).trim().replace(/^.*->\s*/, "");
|
|
199
|
+
if (filePath) {
|
|
200
|
+
changedFiles.push(filePath);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
let diffRes = await ejecutarGit(["diff", "HEAD"], cwd);
|
|
204
|
+
if (!diffRes.ok || !diffRes.stdout) {
|
|
205
|
+
diffRes = await ejecutarGit(["diff"], cwd);
|
|
206
|
+
}
|
|
207
|
+
const rawDiff = diffRes.ok ? diffRes.stdout : "";
|
|
208
|
+
return auditDiffText(rawDiff, changedFiles);
|
|
209
|
+
}
|
|
210
|
+
export function suggestConventionalCommit(files, _insertions, _deletions) {
|
|
211
|
+
if (files.length === 0)
|
|
212
|
+
return "chore: no pending changes";
|
|
213
|
+
const allTests = files.every((f) => f.startsWith("tests/") || f.includes(".test.") || f.includes(".spec."));
|
|
214
|
+
if (allTests) {
|
|
215
|
+
const mod = files[0].replace(/^tests\//, "").replace(/\.(test|spec)\.[a-zA-Z0-9]+$/, "");
|
|
216
|
+
return `test(${mod || "suite"}): add comprehensive test coverage`;
|
|
217
|
+
}
|
|
218
|
+
const allDocs = files.every((f) => f.endsWith(".md") || f.startsWith("docs/"));
|
|
219
|
+
if (allDocs) {
|
|
220
|
+
return "docs: update documentation and project specifications";
|
|
221
|
+
}
|
|
222
|
+
const allTools = files.every((f) => f.includes("tools/") || f.includes("tool"));
|
|
223
|
+
if (allTools) {
|
|
224
|
+
return "feat(tools): improve CLI tools and execution workflows";
|
|
225
|
+
}
|
|
226
|
+
const allUI = files.every((f) => f.includes("ui/") || f.includes("prompt") || f.includes("pie"));
|
|
227
|
+
if (allUI) {
|
|
228
|
+
return "feat(ui): refine interactive terminal interface and widgets";
|
|
229
|
+
}
|
|
230
|
+
const hasNewFeature = files.some((f) => f.startsWith("src/"));
|
|
231
|
+
if (hasNewFeature) {
|
|
232
|
+
return "feat: enhance core functionality and developer experience";
|
|
233
|
+
}
|
|
234
|
+
return "chore: update project files";
|
|
235
|
+
}
|
|
236
|
+
export function formatAuditReport(report) {
|
|
237
|
+
if (report.clean) {
|
|
238
|
+
return chalk.green("\n✔ Repositorio limpio. No hay cambios pendientes para auditar.\n");
|
|
239
|
+
}
|
|
240
|
+
const lines = [];
|
|
241
|
+
lines.push(chalk.bold.hex("#D97757")("\n🦊 AUDITORÍA DE CAMBIOS (PRE-COMMIT)"));
|
|
242
|
+
lines.push(chalk.gray(` ${report.stats.filesChanged} archivos modificados · ${chalk.green(`+${report.stats.insertions}`)} / ${chalk.red(`-${report.stats.deletions}`)}`));
|
|
243
|
+
lines.push("");
|
|
244
|
+
if (report.sensitiveFiles.length > 0 || report.secrets.length > 0) {
|
|
245
|
+
lines.push(chalk.bold.red(" 🚨 ADVERTENCIA DE SEGURIDAD:"));
|
|
246
|
+
for (const sf of report.sensitiveFiles) {
|
|
247
|
+
lines.push(chalk.red(` ✖ Archivo sensible detectado: `) + chalk.bold.white(sf));
|
|
248
|
+
}
|
|
249
|
+
for (const s of report.secrets) {
|
|
250
|
+
const loc = s.line ? `:${s.line}` : "";
|
|
251
|
+
lines.push(chalk.red(` ✖ [${s.message}] en `) +
|
|
252
|
+
chalk.bold.white(`${s.file}${loc}`) +
|
|
253
|
+
chalk.gray(`: "${s.snippet || ""}"`));
|
|
254
|
+
}
|
|
255
|
+
lines.push("");
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
lines.push(chalk.green(" ✔ Seguridad: Sin credenciales ni claves de API expuestas en el diff."));
|
|
259
|
+
}
|
|
260
|
+
if (report.debugLogs.length > 0) {
|
|
261
|
+
lines.push(chalk.yellow(` ⚠ Calidad de código: ${report.debugLogs.length} sentencias de depuración encontradas:`));
|
|
262
|
+
for (const d of report.debugLogs.slice(0, 5)) {
|
|
263
|
+
const loc = d.line ? `:${d.line}` : "";
|
|
264
|
+
lines.push(chalk.gray(` • [${d.message}] en `) +
|
|
265
|
+
chalk.white(`${d.file}${loc}`) +
|
|
266
|
+
chalk.gray(`: "${d.snippet || ""}"`));
|
|
267
|
+
}
|
|
268
|
+
if (report.debugLogs.length > 5) {
|
|
269
|
+
lines.push(chalk.gray(` ... y ${report.debugLogs.length - 5} más.`));
|
|
270
|
+
}
|
|
271
|
+
lines.push("");
|
|
272
|
+
}
|
|
273
|
+
else {
|
|
274
|
+
lines.push(chalk.green(" ✔ Calidad: Sin rastros de console.log ni debugger en líneas nuevas."));
|
|
275
|
+
}
|
|
276
|
+
lines.push("");
|
|
277
|
+
lines.push(chalk.bold.hex("#A855F7")(" 💡 Sugerencia de Commit Semántico (Conventional Commits):"));
|
|
278
|
+
lines.push(` ${chalk.bold.white(report.suggestedCommit)}`);
|
|
279
|
+
lines.push(chalk.gray(` (Usa: /commit "${report.suggestedCommit}")`));
|
|
280
|
+
lines.push("");
|
|
281
|
+
return lines.join("\n");
|
|
282
|
+
}
|
package/dist/tools/runCommand.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
+
import { esWindows, shellPosix } from "../config/plataforma.js";
|
|
2
3
|
import { StringDecoder } from "node:string_decoder";
|
|
3
4
|
/**
|
|
4
5
|
* Ejecucion de comandos de shell.
|
|
@@ -108,9 +109,11 @@ export async function runCommand(command, cwd = process.cwd(), timeoutMs) {
|
|
|
108
109
|
return "Aviso: para esperar, usa 'computer_use' con action 'wait' y ms, en vez de bloquear un shell.";
|
|
109
110
|
}
|
|
110
111
|
const timeout = Math.min(Math.max(timeoutMs || DEFAULT_TIMEOUT_MS, 1_000), MAX_TIMEOUT_MS);
|
|
111
|
-
const isWindows =
|
|
112
|
+
const isWindows = esWindows;
|
|
112
113
|
// Se usa de verdad el shell que toca. En Windows, PowerShell.
|
|
113
|
-
|
|
114
|
+
// En Unix no se pone "/bin/bash" a pelo: en Termux ese archivo no existe y
|
|
115
|
+
// fallarian todos los comandos, no solo alguno. Ver config/plataforma.
|
|
116
|
+
const shellPath = isWindows ? "powershell.exe" : shellPosix("bash");
|
|
114
117
|
const shellArgs = isWindows
|
|
115
118
|
? // -NoProfile para que el perfil del usuario no ensucie la salida ni cueste
|
|
116
119
|
// medio segundo, y -NonInteractive para que cualquier prompt sea un error
|