chocolatito-code 1.6.7 → 1.6.9
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 +447 -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 +99 -6
- 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 +68 -0
- package/dist/tools/gitAudit.js +374 -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/marco.d.ts +1 -0
- package/dist/ui/marco.js +4 -2
- package/dist/ui/permissionPrompt.d.ts +9 -0
- package/dist/ui/permissionPrompt.js +30 -0
- package/dist/ui/pieFijo.d.ts +1 -1
- package/dist/ui/pieFijo.js +1 -1
- package/dist/ui/renderer.js +8 -0
- package/extension/background.js +80 -0
- package/extension/content.js +106 -0
- package/extension/manifest.json +61 -60
- package/package.json +3 -2
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import { ejecutarGit } from "./gitTools.js";
|
|
5
|
+
export const SECRET_PATTERNS = [
|
|
6
|
+
{ name: "Clave de API de OpenAI", pattern: /\bsk-[a-zA-Z0-9]{20,}\b/ },
|
|
7
|
+
{ name: "Clave de API de Anthropic", pattern: /\bsk-ant-[a-zA-Z0-9_-]{20,}\b/ },
|
|
8
|
+
{ name: "Clave de AWS (Access Key)", pattern: /\bAKIA[0-9A-Z]{16}\b/ },
|
|
9
|
+
{ name: "Clave de API de Google", pattern: /\bAIza[0-9A-Za-z\-_]{30,40}\b/ },
|
|
10
|
+
{ name: "Token de acceso de GitHub", pattern: /\bgh[pousr]_[A-Za-z0-9_]{36,}\b/ },
|
|
11
|
+
{ name: "Clave privada (RSA/EC/OpenSSH)", pattern: /-----BEGIN (?:[A-Z ]+)?PRIVATE KEY-----/ },
|
|
12
|
+
{
|
|
13
|
+
name: "Asignación genérica de credencial o token",
|
|
14
|
+
pattern: /(?:secret|password|passwd|api_key|apikey|token|auth_token)\s*[:=]\s*["'][^"'\s]{8,}["']/i,
|
|
15
|
+
},
|
|
16
|
+
];
|
|
17
|
+
export const SENSITIVE_FILE_PATTERN = /(?:^|[/\\])(\.env(?:\.[a-zA-Z0-9_-]+)?|id_rsa|id_ed25519|.*\.pem)$/i;
|
|
18
|
+
export const DEBUG_PATTERNS = [
|
|
19
|
+
{ name: "console.log", pattern: /\bconsole\.log\s*\(/ },
|
|
20
|
+
{ name: "console.debug", pattern: /\bconsole\.debug\s*\(/ },
|
|
21
|
+
// `debugger` como PALABRA sale tambien dentro de una frase: el propio aviso
|
|
22
|
+
// de esta herramienta dice "ni console.log ni debugger" y se denunciaba solo.
|
|
23
|
+
// Un `debugger` de verdad es una sentencia: lleva punto y coma, o esta sola en
|
|
24
|
+
// su linea. Eso es lo que se busca.
|
|
25
|
+
{ name: "debugger", pattern: /(?:^|[;{}])\s*debugger\s*(?:;|$)/ },
|
|
26
|
+
{ name: "print()", pattern: /(?<![\w.])print\s*\(/ },
|
|
27
|
+
{ name: "var_dump", pattern: /\bvar_dump\s*\(/ },
|
|
28
|
+
{ name: "dd()", pattern: /(?<![\w.])dd\s*\(/ },
|
|
29
|
+
];
|
|
30
|
+
/**
|
|
31
|
+
* LA DOCUMENTACION NO ES CODIGO, Y ESO AQUI IMPORTA
|
|
32
|
+
*
|
|
33
|
+
* La primera vez que se paso este auditor por su propio repositorio, se denuncio
|
|
34
|
+
* a si mismo: la linea del README que dice que detecta `console.log`, `debugger`
|
|
35
|
+
* y `print()` contiene, por fuerza, un `console.log`, un `debugger` y un
|
|
36
|
+
* `print()`. Cinco avisos de calidad y una "clave privada" que era la cabecera
|
|
37
|
+
* `-----BEGIN PRIVATE KEY-----` citada dentro de un parrafo.
|
|
38
|
+
*
|
|
39
|
+
* Un auditor que grita en cuanto alguien lo documenta es un auditor que se deja
|
|
40
|
+
* de leer, y entonces no sirve para lo unico que tiene que servir: que el aviso
|
|
41
|
+
* de verdad se vea. Asi que:
|
|
42
|
+
*
|
|
43
|
+
* - Las sentencias de depuracion solo se buscan en archivos de codigo. Un
|
|
44
|
+
* `console.log` dentro de un .md no es un resto de depuracion: es una frase.
|
|
45
|
+
* - En documentacion, lo que va entre comillas invertidas es una cita, no una
|
|
46
|
+
* credencial. Se quitan esos trozos ANTES de buscar secretos.
|
|
47
|
+
*
|
|
48
|
+
* Lo que NO se toca: una clave de verdad pegada en un README sigue saltando. Una
|
|
49
|
+
* clave real ocupa mil y pico caracteres y llega en un bloque cercado o a pelo,
|
|
50
|
+
* nunca dentro de un `span` de una linea, que es lo unico que se ignora.
|
|
51
|
+
*/
|
|
52
|
+
const EXTENSIONES_DE_DOCUMENTACION = new Set([
|
|
53
|
+
"md",
|
|
54
|
+
"markdown",
|
|
55
|
+
"mdx",
|
|
56
|
+
"txt",
|
|
57
|
+
"rst",
|
|
58
|
+
"adoc",
|
|
59
|
+
"org",
|
|
60
|
+
]);
|
|
61
|
+
export function esDocumentacion(archivo) {
|
|
62
|
+
const ext = archivo.split(".").pop()?.toLowerCase() || "";
|
|
63
|
+
return EXTENSIONES_DE_DOCUMENTACION.has(ext);
|
|
64
|
+
}
|
|
65
|
+
/** Quita los trozos entre comillas invertidas: `asi` y ``asi``. */
|
|
66
|
+
export function sinCitasEnLinea(texto) {
|
|
67
|
+
return texto.replace(/``[^`]*``/g, " ").replace(/`[^`]*`/g, " ");
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* SE PUEDE CALLAR EL RUIDO, NUNCA UN SECRETO
|
|
71
|
+
*
|
|
72
|
+
* En un programa de terminal, `console.log` NO es un resto de depuracion: es la
|
|
73
|
+
* interfaz. Este repositorio tiene ocho en `src/index.ts` que son justo eso -el
|
|
74
|
+
* enlace de `--servir`, el aviso de que solo escucha en esta maquina- y salian
|
|
75
|
+
* los ocho en cada auditoria. Ocho avisos que hay que ignorar a mano cada vez
|
|
76
|
+
* son ocho razones para dejar de leer la seccion entera.
|
|
77
|
+
*
|
|
78
|
+
* Adivinar cual es interfaz y cual es un olvido no se puede hacer desde fuera:
|
|
79
|
+
* es la misma llamada. Asi que lo dice el proyecto, en un `.chocolatitoauditignore`
|
|
80
|
+
* al lado del `.gitignore`, una ruta o un patron por linea, y `#` para comentar.
|
|
81
|
+
*
|
|
82
|
+
* Con un limite que no se negocia: **esto solo calla la seccion de calidad**. Un
|
|
83
|
+
* secreto, un archivo sensible o una clave privada se avisan siempre, este la
|
|
84
|
+
* ruta ignorada o no. Una herramienta que se puede configurar para callarse una
|
|
85
|
+
* credencial es peor que no tenerla, porque da la tranquilidad sin el aviso.
|
|
86
|
+
*
|
|
87
|
+
* Y lo que se calla se cuenta: el informe dice cuantos silencio, para que nadie
|
|
88
|
+
* descubra el fichero un año despues sin saber que estaba puesto.
|
|
89
|
+
*/
|
|
90
|
+
export const ARCHIVO_DE_IGNORADOS = ".chocolatitoauditignore";
|
|
91
|
+
export function leerPatronesIgnorados(cwd) {
|
|
92
|
+
try {
|
|
93
|
+
const crudo = fs.readFileSync(path.join(cwd, ARCHIVO_DE_IGNORADOS), "utf-8");
|
|
94
|
+
return crudo
|
|
95
|
+
.split(/\r?\n/)
|
|
96
|
+
.map((l) => l.trim())
|
|
97
|
+
.filter((l) => l.length > 0 && !l.startsWith("#"));
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return [];
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/** Un patron de `.chocolatitoauditignore` contra una ruta del diff. */
|
|
104
|
+
export function rutaIgnorada(archivo, patrones) {
|
|
105
|
+
if (!archivo || patrones.length === 0)
|
|
106
|
+
return false;
|
|
107
|
+
const ruta = archivo.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
108
|
+
const base = ruta.split("/").pop() || ruta;
|
|
109
|
+
for (const crudo of patrones) {
|
|
110
|
+
const patron = crudo.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
111
|
+
// Una carpeta: "src/ui/" tapa todo lo que hay dentro.
|
|
112
|
+
if (patron.endsWith("/")) {
|
|
113
|
+
if (ruta.startsWith(patron))
|
|
114
|
+
return true;
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
// "**" cruza barras, "*" no: src/*.ts no baja a src/ui/marco.ts, src/**/*.ts si.
|
|
118
|
+
const comoRegex = patron
|
|
119
|
+
.split("**")
|
|
120
|
+
.map((trozo) => trozo
|
|
121
|
+
.replace(/[.+^$(){}|[\]\\]/g, (m) => "\\" + m)
|
|
122
|
+
.replace(/\*/g, "[^/]*")
|
|
123
|
+
.replace(/\?/g, "[^/]"))
|
|
124
|
+
.join(".*");
|
|
125
|
+
const re = new RegExp(`^${comoRegex}$`);
|
|
126
|
+
if (re.test(ruta))
|
|
127
|
+
return true;
|
|
128
|
+
// Un patron sin barras ("*.min.js") mira tambien solo el nombre.
|
|
129
|
+
if (!patron.includes("/") && re.test(base))
|
|
130
|
+
return true;
|
|
131
|
+
// Una carpeta escrita sin barra final ("src/ui") tapa igual lo de dentro.
|
|
132
|
+
if (ruta.startsWith(`${patron}/`))
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
export function auditDiffText(rawDiff, changedFiles = [], patronesIgnorados = []) {
|
|
138
|
+
const secrets = [];
|
|
139
|
+
const debugLogs = [];
|
|
140
|
+
const sensitiveFiles = [];
|
|
141
|
+
let debugSilenciados = 0;
|
|
142
|
+
for (const f of changedFiles) {
|
|
143
|
+
if (SENSITIVE_FILE_PATTERN.test(f) && !sensitiveFiles.includes(f)) {
|
|
144
|
+
sensitiveFiles.push(f);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
let currentFile = "";
|
|
148
|
+
let currentLine = 0;
|
|
149
|
+
let insertions = 0;
|
|
150
|
+
let deletions = 0;
|
|
151
|
+
const diffFiles = new Set(changedFiles);
|
|
152
|
+
const lines = rawDiff.split("\n");
|
|
153
|
+
for (const line of lines) {
|
|
154
|
+
if (line.startsWith("diff --git ")) {
|
|
155
|
+
const parts = line.split(" ");
|
|
156
|
+
currentFile = parts[parts.length - 1]?.replace(/^b\//, "") || "";
|
|
157
|
+
if (currentFile)
|
|
158
|
+
diffFiles.add(currentFile);
|
|
159
|
+
if (SENSITIVE_FILE_PATTERN.test(currentFile) && !sensitiveFiles.includes(currentFile)) {
|
|
160
|
+
sensitiveFiles.push(currentFile);
|
|
161
|
+
}
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (line.startsWith("+++ b/")) {
|
|
165
|
+
currentFile = line.slice(6).trim();
|
|
166
|
+
if (currentFile)
|
|
167
|
+
diffFiles.add(currentFile);
|
|
168
|
+
if (SENSITIVE_FILE_PATTERN.test(currentFile) && !sensitiveFiles.includes(currentFile)) {
|
|
169
|
+
sensitiveFiles.push(currentFile);
|
|
170
|
+
}
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
if (line.startsWith("@@ ")) {
|
|
174
|
+
const hunkMatch = line.match(/@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@/);
|
|
175
|
+
if (hunkMatch) {
|
|
176
|
+
currentLine = parseInt(hunkMatch[1], 10) - 1;
|
|
177
|
+
}
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
if (line.startsWith("+") && !line.startsWith("+++")) {
|
|
181
|
+
insertions++;
|
|
182
|
+
currentLine++;
|
|
183
|
+
const addedContent = line.slice(1).trim();
|
|
184
|
+
const enDocumentacion = esDocumentacion(currentFile || "");
|
|
185
|
+
const paraSecretos = enDocumentacion ? sinCitasEnLinea(addedContent) : addedContent;
|
|
186
|
+
for (const sp of SECRET_PATTERNS) {
|
|
187
|
+
if (sp.pattern.test(paraSecretos)) {
|
|
188
|
+
secrets.push({
|
|
189
|
+
type: "secret",
|
|
190
|
+
file: currentFile || "diff",
|
|
191
|
+
line: currentLine,
|
|
192
|
+
message: sp.name,
|
|
193
|
+
snippet: addedContent.length > 50 ? `${addedContent.slice(0, 47)}…` : addedContent,
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
const silenciado = rutaIgnorada(currentFile || "", patronesIgnorados);
|
|
198
|
+
for (const dp of DEBUG_PATTERNS) {
|
|
199
|
+
if (enDocumentacion)
|
|
200
|
+
break;
|
|
201
|
+
if (silenciado) {
|
|
202
|
+
// Se cuenta aunque no se liste: callar no es esconder.
|
|
203
|
+
if (dp.pattern.test(addedContent))
|
|
204
|
+
debugSilenciados++;
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
if (dp.pattern.test(addedContent)) {
|
|
208
|
+
debugLogs.push({
|
|
209
|
+
type: "debug",
|
|
210
|
+
file: currentFile || "diff",
|
|
211
|
+
line: currentLine,
|
|
212
|
+
message: dp.name,
|
|
213
|
+
snippet: addedContent.length > 50 ? `${addedContent.slice(0, 47)}…` : addedContent,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
else if (line.startsWith("-") && !line.startsWith("---")) {
|
|
219
|
+
deletions++;
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
currentLine++;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
const allFiles = Array.from(diffFiles);
|
|
226
|
+
const clean = allFiles.length === 0 && insertions === 0 && deletions === 0 && secrets.length === 0 && sensitiveFiles.length === 0;
|
|
227
|
+
const suggestedCommit = suggestConventionalCommit(allFiles, insertions, deletions);
|
|
228
|
+
return {
|
|
229
|
+
ok: true,
|
|
230
|
+
clean,
|
|
231
|
+
stats: {
|
|
232
|
+
filesChanged: allFiles.length,
|
|
233
|
+
insertions,
|
|
234
|
+
additions: insertions,
|
|
235
|
+
deletions,
|
|
236
|
+
files: allFiles,
|
|
237
|
+
},
|
|
238
|
+
secrets,
|
|
239
|
+
debugLogs,
|
|
240
|
+
residualLogs: debugLogs,
|
|
241
|
+
debugSilenciados,
|
|
242
|
+
sensitiveFiles,
|
|
243
|
+
suggestedCommit,
|
|
244
|
+
rawDiff,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
export async function auditGitDiff(cwd = process.cwd(), mockDiff) {
|
|
248
|
+
if (mockDiff !== undefined) {
|
|
249
|
+
return auditDiffText(mockDiff);
|
|
250
|
+
}
|
|
251
|
+
const statusRes = await ejecutarGit(["status", "--porcelain"], cwd);
|
|
252
|
+
if (!statusRes.ok) {
|
|
253
|
+
return {
|
|
254
|
+
ok: false,
|
|
255
|
+
clean: true,
|
|
256
|
+
stats: { filesChanged: 0, insertions: 0, additions: 0, deletions: 0, files: [] },
|
|
257
|
+
secrets: [],
|
|
258
|
+
debugLogs: [],
|
|
259
|
+
residualLogs: [],
|
|
260
|
+
debugSilenciados: 0,
|
|
261
|
+
sensitiveFiles: [],
|
|
262
|
+
suggestedCommit: "chore: update project",
|
|
263
|
+
rawDiff: statusRes.error,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
const statusLines = statusRes.stdout.split("\n").filter((l) => l.trim().length > 0);
|
|
267
|
+
if (statusLines.length === 0) {
|
|
268
|
+
return {
|
|
269
|
+
ok: true,
|
|
270
|
+
clean: true,
|
|
271
|
+
stats: { filesChanged: 0, insertions: 0, additions: 0, deletions: 0, files: [] },
|
|
272
|
+
secrets: [],
|
|
273
|
+
debugLogs: [],
|
|
274
|
+
residualLogs: [],
|
|
275
|
+
debugSilenciados: 0,
|
|
276
|
+
sensitiveFiles: [],
|
|
277
|
+
suggestedCommit: "chore: no pending changes",
|
|
278
|
+
rawDiff: "",
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
const changedFiles = [];
|
|
282
|
+
for (const sl of statusLines) {
|
|
283
|
+
const filePath = sl.slice(3).trim().replace(/^.*->\s*/, "");
|
|
284
|
+
if (filePath) {
|
|
285
|
+
changedFiles.push(filePath);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
let diffRes = await ejecutarGit(["diff", "HEAD"], cwd);
|
|
289
|
+
if (!diffRes.ok || !diffRes.stdout) {
|
|
290
|
+
diffRes = await ejecutarGit(["diff"], cwd);
|
|
291
|
+
}
|
|
292
|
+
const rawDiff = diffRes.ok ? diffRes.stdout : "";
|
|
293
|
+
return auditDiffText(rawDiff, changedFiles, leerPatronesIgnorados(cwd));
|
|
294
|
+
}
|
|
295
|
+
export function suggestConventionalCommit(files, _insertions, _deletions) {
|
|
296
|
+
if (files.length === 0)
|
|
297
|
+
return "chore: no pending changes";
|
|
298
|
+
const allTests = files.every((f) => f.startsWith("tests/") || f.includes(".test.") || f.includes(".spec."));
|
|
299
|
+
if (allTests) {
|
|
300
|
+
const mod = files[0].replace(/^tests\//, "").replace(/\.(test|spec)\.[a-zA-Z0-9]+$/, "");
|
|
301
|
+
return `test(${mod || "suite"}): add comprehensive test coverage`;
|
|
302
|
+
}
|
|
303
|
+
const allDocs = files.every((f) => f.endsWith(".md") || f.startsWith("docs/"));
|
|
304
|
+
if (allDocs) {
|
|
305
|
+
return "docs: update documentation and project specifications";
|
|
306
|
+
}
|
|
307
|
+
const allTools = files.every((f) => f.includes("tools/") || f.includes("tool"));
|
|
308
|
+
if (allTools) {
|
|
309
|
+
return "feat(tools): improve CLI tools and execution workflows";
|
|
310
|
+
}
|
|
311
|
+
const allUI = files.every((f) => f.includes("ui/") || f.includes("prompt") || f.includes("pie"));
|
|
312
|
+
if (allUI) {
|
|
313
|
+
return "feat(ui): refine interactive terminal interface and widgets";
|
|
314
|
+
}
|
|
315
|
+
const hasNewFeature = files.some((f) => f.startsWith("src/"));
|
|
316
|
+
if (hasNewFeature) {
|
|
317
|
+
return "feat: enhance core functionality and developer experience";
|
|
318
|
+
}
|
|
319
|
+
return "chore: update project files";
|
|
320
|
+
}
|
|
321
|
+
export function formatAuditReport(report) {
|
|
322
|
+
if (report.clean) {
|
|
323
|
+
return chalk.green("\n✔ Repositorio limpio. No hay cambios pendientes para auditar.\n");
|
|
324
|
+
}
|
|
325
|
+
const lines = [];
|
|
326
|
+
lines.push(chalk.bold.hex("#D97757")("\n🦊 AUDITORÍA DE CAMBIOS (PRE-COMMIT)"));
|
|
327
|
+
lines.push(chalk.gray(` ${report.stats.filesChanged} archivos modificados · ${chalk.green(`+${report.stats.insertions}`)} / ${chalk.red(`-${report.stats.deletions}`)}`));
|
|
328
|
+
lines.push("");
|
|
329
|
+
if (report.sensitiveFiles.length > 0 || report.secrets.length > 0) {
|
|
330
|
+
lines.push(chalk.bold.red(" 🚨 ADVERTENCIA DE SEGURIDAD:"));
|
|
331
|
+
for (const sf of report.sensitiveFiles) {
|
|
332
|
+
lines.push(chalk.red(` ✖ Archivo sensible detectado: `) + chalk.bold.white(sf));
|
|
333
|
+
}
|
|
334
|
+
for (const s of report.secrets) {
|
|
335
|
+
const loc = s.line ? `:${s.line}` : "";
|
|
336
|
+
lines.push(chalk.red(` ✖ [${s.message}] en `) +
|
|
337
|
+
chalk.bold.white(`${s.file}${loc}`) +
|
|
338
|
+
chalk.gray(`: "${s.snippet || ""}"`));
|
|
339
|
+
}
|
|
340
|
+
lines.push("");
|
|
341
|
+
}
|
|
342
|
+
else {
|
|
343
|
+
lines.push(chalk.green(" ✔ Seguridad: Sin credenciales ni claves de API expuestas en el diff."));
|
|
344
|
+
}
|
|
345
|
+
if (report.debugLogs.length > 0) {
|
|
346
|
+
lines.push(chalk.yellow(` ⚠ Calidad de código: ${report.debugLogs.length} sentencias de depuración encontradas:`));
|
|
347
|
+
for (const d of report.debugLogs.slice(0, 5)) {
|
|
348
|
+
const loc = d.line ? `:${d.line}` : "";
|
|
349
|
+
lines.push(chalk.gray(` • [${d.message}] en `) +
|
|
350
|
+
chalk.white(`${d.file}${loc}`) +
|
|
351
|
+
chalk.gray(`: "${d.snippet || ""}"`));
|
|
352
|
+
}
|
|
353
|
+
if (report.debugLogs.length > 5) {
|
|
354
|
+
lines.push(chalk.gray(` ... y ${report.debugLogs.length - 5} más.`));
|
|
355
|
+
}
|
|
356
|
+
if (report.debugSilenciados > 0) {
|
|
357
|
+
lines.push(chalk.gray(` (${report.debugSilenciados} más en rutas de ${ARCHIVO_DE_IGNORADOS}, sin listar.)`));
|
|
358
|
+
}
|
|
359
|
+
lines.push("");
|
|
360
|
+
}
|
|
361
|
+
else if (report.debugSilenciados > 0) {
|
|
362
|
+
lines.push(chalk.green(" ✔ Calidad: Sin rastros nuevos de console.log ni debugger") +
|
|
363
|
+
chalk.gray(` (${report.debugSilenciados} en rutas de ${ARCHIVO_DE_IGNORADOS}, sin listar).`));
|
|
364
|
+
}
|
|
365
|
+
else {
|
|
366
|
+
lines.push(chalk.green(" ✔ Calidad: Sin rastros de console.log ni debugger en líneas nuevas."));
|
|
367
|
+
}
|
|
368
|
+
lines.push("");
|
|
369
|
+
lines.push(chalk.bold.hex("#A855F7")(" 💡 Sugerencia de Commit Semántico (Conventional Commits):"));
|
|
370
|
+
lines.push(` ${chalk.bold.white(report.suggestedCommit)}`);
|
|
371
|
+
lines.push(chalk.gray(` (Usa: /commit "${report.suggestedCommit}")`));
|
|
372
|
+
lines.push("");
|
|
373
|
+
return lines.join("\n");
|
|
374
|
+
}
|
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
|
package/dist/tools/runner.js
CHANGED
|
@@ -25,6 +25,7 @@ import { globalUndoManager } from "../agent/undoManager.js";
|
|
|
25
25
|
import { validateFileSyntax } from "../agent/syntaxValidator.js";
|
|
26
26
|
import { fileTracker } from "../agent/fileTracker.js";
|
|
27
27
|
import { mcpManager } from "../mcp/manager.js";
|
|
28
|
+
import { taskManager } from "./backgroundTask.js";
|
|
28
29
|
import fs from "node:fs";
|
|
29
30
|
import path from "node:path";
|
|
30
31
|
/**
|
|
@@ -82,10 +83,12 @@ export async function executeToolCall(name, args, cwd = process.cwd(), apiKey, m
|
|
|
82
83
|
break;
|
|
83
84
|
}
|
|
84
85
|
case "save_memory":
|
|
85
|
-
|
|
86
|
+
memoryManager.setCwd(cwd);
|
|
87
|
+
rawResult = memoryManager.saveMemory(args.topic, args.fact, args.scope);
|
|
86
88
|
break;
|
|
87
89
|
case "read_memory":
|
|
88
|
-
|
|
90
|
+
memoryManager.setCwd(cwd);
|
|
91
|
+
rawResult = memoryManager.readMemory(args.topic, args.scope);
|
|
89
92
|
break;
|
|
90
93
|
case "spawn_agent": {
|
|
91
94
|
if (!apiKey) {
|
|
@@ -201,6 +204,37 @@ export async function executeToolCall(name, args, cwd = process.cwd(), apiKey, m
|
|
|
201
204
|
case "run_command":
|
|
202
205
|
rawResult = await runCommand(args.command, cwd, args.timeout);
|
|
203
206
|
break;
|
|
207
|
+
case "start_background_task": {
|
|
208
|
+
const res = await taskManager.startTask(args.command, args.cwd || cwd, args.description);
|
|
209
|
+
rawResult = `Tarea iniciada en segundo plano:\n- ID: ${res.taskId}\n- PID: ${res.pid}\n- Estado: ${res.status}\n- Log: ${res.logPath}`;
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
case "read_task_output": {
|
|
213
|
+
const res = taskManager.readTaskOutput(args.taskId, args.lines, args.offset);
|
|
214
|
+
if (res.status === "not_found") {
|
|
215
|
+
rawResult = res.output;
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
rawResult = `[Tarea ${res.taskId}] Estado: ${res.status} (Exit Code: ${res.exitCode !== null ? res.exitCode : "N/A"})\nTotal de líneas: ${res.totalLines}\n--- Salida ---\n${res.output || "(sin salida aún)"}`;
|
|
219
|
+
}
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
case "list_background_tasks": {
|
|
223
|
+
const tasks = taskManager.listTasks();
|
|
224
|
+
if (tasks.length === 0) {
|
|
225
|
+
rawResult = "No hay tareas en segundo plano registradas.";
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
const lines = tasks.map((t) => `- [${t.taskId}] PID ${t.pid} | ${t.status.toUpperCase()} | Ejecución: ${t.runtimeSeconds}s | Comando: "${t.command}"${t.description ? ` (${t.description})` : ""}${t.exitCode !== null ? ` | Exit code: ${t.exitCode}` : ""}`);
|
|
229
|
+
rawResult = `Tareas en segundo plano (${tasks.length}):\n${lines.join("\n")}`;
|
|
230
|
+
}
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
case "stop_background_task": {
|
|
234
|
+
const res = await taskManager.stopTask(args.taskId, args.force);
|
|
235
|
+
rawResult = res.message;
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
204
238
|
case "list_dir":
|
|
205
239
|
rawResult = await listDir(args.dirPath || ".", cwd);
|
|
206
240
|
break;
|
package/dist/tools/safety.d.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* estado. En cambio dos escrituras seguidas SI pueden pisarse, y el orden
|
|
8
8
|
* importa, asi que esas van una detras de otra.
|
|
9
9
|
*/
|
|
10
|
+
export declare const READ_ONLY_TOOLS: Set<string>;
|
|
10
11
|
export declare function isReadOnly(toolName: string): boolean;
|
|
11
12
|
/**
|
|
12
13
|
* Un lote solo se paraleliza si TODAS sus llamadas son de solo lectura y hay
|
package/dist/tools/safety.js
CHANGED
|
@@ -18,7 +18,10 @@ const READ_ONLY = new Set([
|
|
|
18
18
|
"web_search",
|
|
19
19
|
"read_memory",
|
|
20
20
|
"use_skill",
|
|
21
|
+
"read_task_output",
|
|
22
|
+
"list_background_tasks",
|
|
21
23
|
]);
|
|
24
|
+
export const READ_ONLY_TOOLS = READ_ONLY;
|
|
22
25
|
export function isReadOnly(toolName) {
|
|
23
26
|
return READ_ONLY.has(toolName);
|
|
24
27
|
}
|
package/dist/tools/todoTool.d.ts
CHANGED
|
@@ -5,4 +5,6 @@ export interface TaskItem {
|
|
|
5
5
|
}
|
|
6
6
|
export declare function setSessionTasks(tasks: TaskItem[]): string;
|
|
7
7
|
export declare function getSessionTasks(): TaskItem[];
|
|
8
|
+
export declare function renderTaskWidget(tasks?: TaskItem[]): string | null;
|
|
9
|
+
export declare function sincronizarTareasConPie(tasks?: TaskItem[]): void;
|
|
8
10
|
export declare function renderTaskList(tasks: TaskItem[]): string;
|
package/dist/tools/todoTool.js
CHANGED
|
@@ -1,12 +1,41 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
|
+
import { fijarSeccion, quitarSeccion } from "../ui/pieFijo.js";
|
|
2
3
|
let sessionTasks = [];
|
|
3
4
|
export function setSessionTasks(tasks) {
|
|
4
5
|
sessionTasks = tasks;
|
|
6
|
+
sincronizarTareasConPie(sessionTasks);
|
|
5
7
|
return renderTaskList(sessionTasks);
|
|
6
8
|
}
|
|
7
9
|
export function getSessionTasks() {
|
|
8
10
|
return sessionTasks;
|
|
9
11
|
}
|
|
12
|
+
export function renderTaskWidget(tasks) {
|
|
13
|
+
const list = tasks !== undefined ? tasks : sessionTasks;
|
|
14
|
+
if (!list || list.length === 0)
|
|
15
|
+
return null;
|
|
16
|
+
const total = list.length;
|
|
17
|
+
const completed = list.filter((t) => t.status === "completed").length;
|
|
18
|
+
const inProgress = list.find((t) => t.status === "in_progress");
|
|
19
|
+
return (chalk.hex("#D97757")("⎿") +
|
|
20
|
+
" " +
|
|
21
|
+
chalk.green("✔") +
|
|
22
|
+
` ${completed}/${total} tareas` +
|
|
23
|
+
(inProgress
|
|
24
|
+
? chalk.gray(" · ") + chalk.hex("#D97757")("En curso: ") + chalk.bold.white(inProgress.text)
|
|
25
|
+
: completed === total
|
|
26
|
+
? chalk.green(" · Completado")
|
|
27
|
+
: ""));
|
|
28
|
+
}
|
|
29
|
+
export function sincronizarTareasConPie(tasks) {
|
|
30
|
+
const list = tasks !== undefined ? tasks : sessionTasks;
|
|
31
|
+
const widget = renderTaskWidget(list);
|
|
32
|
+
if (widget) {
|
|
33
|
+
fijarSeccion("tareas", [widget]);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
quitarSeccion("tareas");
|
|
37
|
+
}
|
|
38
|
+
}
|
|
10
39
|
export function renderTaskList(tasks) {
|
|
11
40
|
if (tasks.length === 0)
|
|
12
41
|
return "No hay tareas registradas.";
|
|
@@ -111,6 +111,8 @@ export const COMPUTER_USE_TOOL = {
|
|
|
111
111
|
"scroll",
|
|
112
112
|
"type",
|
|
113
113
|
"key",
|
|
114
|
+
"hotkey",
|
|
115
|
+
"press",
|
|
114
116
|
"wait",
|
|
115
117
|
"wait_change",
|
|
116
118
|
"cursor_position",
|
|
@@ -183,11 +185,14 @@ export const CHROME_TOOL = {
|
|
|
183
185
|
"status", "tabs", "open", "select_tab", "navigate", "snapshot",
|
|
184
186
|
"click", "type", "press", "get_text", "eval", "wait_for",
|
|
185
187
|
"screenshot", "notice", "close_tab", "done",
|
|
188
|
+
"console_logs", "network_errors",
|
|
186
189
|
],
|
|
187
190
|
description: "status: comprueba que la extension esta conectada. tabs: lista las pestanas del usuario. " +
|
|
188
191
|
"open: abre una pestana nueva SIN quitarle la pantalla al usuario. select_tab: elige una ya abierta. " +
|
|
189
192
|
"snapshot: LEE la pagina y numera los elementos. click/type: actuan sobre un ref. " +
|
|
190
193
|
"wait_for: espera un texto o unos ms. screenshot: captura la pestana aunque no este visible y la describe. " +
|
|
194
|
+
"console_logs: obtiene errores y advertencias de consola y excepciones no controladas de la pagina. " +
|
|
195
|
+
"network_errors: obtiene peticiones de red HTTP fallidas (4xx/5xx o errores de conexion). " +
|
|
191
196
|
"notice: cambia el texto del aviso flotante. close_tab: cierra una pestaña que abriste. done: cierra el trabajo y quita el aviso.",
|
|
192
197
|
},
|
|
193
198
|
url: { type: "string", description: "URL para open o navigate." },
|
|
@@ -212,6 +217,10 @@ export const CHROME_TOOL = {
|
|
|
212
217
|
analyze: { type: "boolean", description: "false para no gastar tokens de vision." },
|
|
213
218
|
outputPath: { type: "string", description: "Ruta donde guardar la captura." },
|
|
214
219
|
note: { type: "string", description: "Texto inicial del aviso flotante al abrir la pestana." },
|
|
220
|
+
clear: {
|
|
221
|
+
type: "boolean",
|
|
222
|
+
description: "true para limpiar el buffer tras consultar (en console_logs o network_errors).",
|
|
223
|
+
},
|
|
215
224
|
},
|
|
216
225
|
required: ["action"],
|
|
217
226
|
},
|
|
@@ -283,9 +283,14 @@ public static class Choco
|
|
|
283
283
|
double scale = 1.0;
|
|
284
284
|
int longEdge = Math.Max(w, h);
|
|
285
285
|
if (maxLongEdge > 0 && longEdge > maxLongEdge) scale = (double)maxLongEdge / (double)longEdge;
|
|
286
|
+
ImageFormat fmt = ImageFormat.Png;
|
|
287
|
+
if (path.EndsWith(".jpg", StringComparison.OrdinalIgnoreCase) || path.EndsWith(".jpeg", StringComparison.OrdinalIgnoreCase))
|
|
288
|
+
{
|
|
289
|
+
fmt = ImageFormat.Jpeg;
|
|
290
|
+
}
|
|
286
291
|
if (scale >= 0.999)
|
|
287
292
|
{
|
|
288
|
-
bmp.Save(path,
|
|
293
|
+
bmp.Save(path, fmt);
|
|
289
294
|
return 1.0;
|
|
290
295
|
}
|
|
291
296
|
int nw = (int)Math.Round(w * scale), nh = (int)Math.Round(h * scale);
|
|
@@ -294,7 +299,7 @@ public static class Choco
|
|
|
294
299
|
{
|
|
295
300
|
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
|
|
296
301
|
g.DrawImage(bmp, 0, 0, nw, nh);
|
|
297
|
-
dst.Save(path,
|
|
302
|
+
dst.Save(path, fmt);
|
|
298
303
|
}
|
|
299
304
|
return scale;
|
|
300
305
|
}
|
package/dist/ui/comandos.js
CHANGED
|
@@ -35,6 +35,8 @@ export const SLASH_COMMANDS = [
|
|
|
35
35
|
{ name: "/goal", description: "Ejecutar meta compleja autónoma con auto-corrección", args: "<meta>" },
|
|
36
36
|
{ name: "/compact", description: "Comprimir contexto y optimizar memoria de la sesión" },
|
|
37
37
|
{ name: "/diff", description: "Ver diferencias de código (Git diff) del proyecto" },
|
|
38
|
+
{ name: "/audit", description: "Auditar git diff en busca de secretos, logs residuales y sugerir commit", args: "[review]" },
|
|
39
|
+
{ name: "/review", description: "Revisar cambios pendientes de git (alias de /audit)" },
|
|
38
40
|
{ name: "/commit", description: "Crear un commit en Git con mensaje semántico", args: "<mensaje>" },
|
|
39
41
|
{ name: "/cd", description: "Cambiar el directorio de trabajo activo", args: "<ruta>" },
|
|
40
42
|
{ name: "/uso", description: "Ver cuánto llevas consumido de tu cuenta" },
|
package/dist/ui/marco.d.ts
CHANGED
package/dist/ui/marco.js
CHANGED
|
@@ -214,7 +214,9 @@ export function buildPromptFrame(opts) {
|
|
|
214
214
|
// Dos, no una. Con una, la caja seguia leyendose pegada a lo de arriba: "pusiste
|
|
215
215
|
// una linea de espacio, que sea bien pues". Con dos se separa de verdad, y es
|
|
216
216
|
// el mismo aire que hay al arrancar entre el zorro y el aviso.
|
|
217
|
-
const
|
|
217
|
+
const widgetLines = opts.taskWidget && opts.taskWidget.trim().length > 0 ? [opts.taskWidget] : [];
|
|
218
|
+
const widgetRows = widgetLines.reduce((n, l) => n + physicalRows(l, cols), 0);
|
|
219
|
+
const lines = ["", "", ...widgetLines, bar, inputLine, ...dropdownLines, bar, footerLine];
|
|
218
220
|
// 4. Donde queda el cursor, en filas fisicas. Antes se hacia cursorCol =
|
|
219
221
|
// total % cols: aplicaba el modulo pero nunca sumaba la fila, asi que en
|
|
220
222
|
// cuanto la entrada se partia el acento se iba a escribir a otra linea.
|
|
@@ -222,7 +224,7 @@ export function buildPromptFrame(opts) {
|
|
|
222
224
|
const segments = typed.split("\n");
|
|
223
225
|
// La fila de aire de arriba cuenta: sin sumarla, el cursor se dibuja una fila
|
|
224
226
|
// por encima de donde se escribe.
|
|
225
|
-
let cursorRow = 2 + physicalRows(bar, cols);
|
|
227
|
+
let cursorRow = 2 + widgetRows + physicalRows(bar, cols);
|
|
226
228
|
for (let i = 0; i < segments.length - 1; i++) {
|
|
227
229
|
cursorRow += Math.max(1, Math.ceil(((i === 0 ? symbolWidth : 0) + segments[i].length) / cols));
|
|
228
230
|
}
|
|
@@ -26,3 +26,12 @@ export declare function buildPermissionBox(toolName: string, args: Record<string
|
|
|
26
26
|
* tarea tal cual y no cuesta nada. Ver ui/ink/prestamo.ts.
|
|
27
27
|
*/
|
|
28
28
|
export declare function askToolPermission(toolName: string, args: Record<string, any>, cwd?: string): Promise<PermissionPromptResult>;
|
|
29
|
+
/**
|
|
30
|
+
* El mismo contenido del dialogo del terminal, sin color y sin marco, para
|
|
31
|
+
* pintarlo en HTML.
|
|
32
|
+
*
|
|
33
|
+
* Se reutiliza `details()` a proposito: dos sitios decidiendo por separado que
|
|
34
|
+
* hay que enseñar antes de aprobar un borrado acaban discrepando, y el que
|
|
35
|
+
* discrepa siempre es el que menos se mira.
|
|
36
|
+
*/
|
|
37
|
+
export declare function detallesEnTextoPlano(toolName: string, args: Record<string, any>, cwd?: string): string[];
|