chocolatito-code 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +155 -0
- package/dist/agent/compaction.d.ts +65 -0
- package/dist/agent/compaction.js +134 -0
- package/dist/agent/context.d.ts +3 -0
- package/dist/agent/context.js +35 -0
- package/dist/agent/loop.d.ts +91 -2
- package/dist/agent/loop.js +642 -91
- package/dist/agent/loopDetector.d.ts +57 -0
- package/dist/agent/loopDetector.js +0 -0
- package/dist/agent/mentions.d.ts +13 -0
- package/dist/agent/mentions.js +49 -0
- package/dist/agent/repoMap.d.ts +117 -0
- package/dist/agent/repoMap.js +560 -0
- package/dist/agent/result.d.ts +64 -0
- package/dist/agent/result.js +48 -0
- package/dist/agent/retry.d.ts +35 -0
- package/dist/agent/retry.js +123 -4
- package/dist/agent/salidaDelAgente.d.ts +43 -0
- package/dist/agent/salidaDelAgente.js +53 -0
- package/dist/agent/subagent.js +62 -15
- package/dist/agent/syntaxValidator.js +288 -52
- package/dist/agent/toolGate.d.ts +58 -0
- package/dist/agent/toolGate.js +111 -0
- package/dist/agent/tracker.js +4 -7
- package/dist/agent/undoManager.d.ts +44 -11
- package/dist/agent/undoManager.js +95 -24
- package/dist/agent/usageMeter.d.ts +56 -0
- package/dist/agent/usageMeter.js +51 -0
- package/dist/agent/verifier.d.ts +49 -0
- package/dist/agent/verifier.js +158 -0
- package/dist/config/constants.js +14 -0
- package/dist/config/engine.d.ts +19 -0
- package/dist/config/engine.js +35 -3
- package/dist/config/license.d.ts +5 -0
- package/dist/config/license.js +107 -9
- package/dist/config/limits.d.ts +28 -0
- package/dist/config/limits.js +51 -0
- package/dist/config/nodeVersion.d.ts +13 -0
- package/dist/config/nodeVersion.js +26 -0
- package/dist/config/permissions.d.ts +76 -1
- package/dist/config/permissions.js +379 -23
- package/dist/config/quota.js +24 -1
- package/dist/config/updater.d.ts +68 -0
- package/dist/config/updater.js +215 -0
- package/dist/hooks/manager.js +159 -14
- package/dist/index.js +240 -37
- package/dist/mcp/client.d.ts +50 -0
- package/dist/mcp/client.js +155 -19
- package/dist/mcp/ide.d.ts +76 -0
- package/dist/mcp/ide.js +160 -0
- package/dist/mcp/manager.js +9 -0
- package/dist/prompts/systemPrompt.js +12 -1
- package/dist/sessions/manager.d.ts +12 -0
- package/dist/sessions/manager.js +33 -5
- package/dist/sessions/resume.d.ts +36 -0
- package/dist/sessions/resume.js +43 -0
- package/dist/skills/manager.d.ts +1 -0
- package/dist/skills/manager.js +9 -0
- package/dist/tools/binary.d.ts +46 -0
- package/dist/tools/binary.js +100 -0
- package/dist/tools/browserCdp.js +13 -1
- package/dist/tools/browserExtension.d.ts +5 -0
- package/dist/tools/browserExtension.js +237 -22
- package/dist/tools/browserSession.d.ts +23 -0
- package/dist/tools/browserSession.js +28 -0
- package/dist/tools/computerUse.js +33 -1
- package/dist/tools/createImage.d.ts +11 -0
- package/dist/tools/createImage.js +13 -1
- package/dist/tools/definitions.js +17 -4
- package/dist/tools/editDiagnosis.d.ts +54 -0
- package/dist/tools/editDiagnosis.js +142 -0
- package/dist/tools/editFile.d.ts +1 -0
- package/dist/tools/editFile.js +118 -62
- package/dist/tools/findFiles.d.ts +21 -0
- package/dist/tools/findFiles.js +75 -6
- package/dist/tools/getSystemInfo.js +15 -7
- package/dist/tools/gitTools.d.ts +24 -0
- package/dist/tools/gitTools.js +87 -26
- package/dist/tools/grepSearch.js +111 -23
- package/dist/tools/moveCopyFile.d.ts +18 -1
- package/dist/tools/moveCopyFile.js +74 -4
- package/dist/tools/patchCascade.d.ts +57 -0
- package/dist/tools/patchCascade.js +338 -0
- package/dist/tools/runCommand.js +213 -25
- package/dist/tools/runner.js +58 -5
- package/dist/tools/toolDefsComputer.js +12 -2
- package/dist/tools/truncator.d.ts +27 -1
- package/dist/tools/truncator.js +117 -11
- package/dist/tools/viewFile.js +15 -11
- package/dist/tools/visionBridge.js +7 -0
- package/dist/tools/webSearch.d.ts +21 -0
- package/dist/tools/webSearch.js +125 -14
- package/dist/tools/writeFile.d.ts +16 -1
- package/dist/tools/writeFile.js +40 -2
- package/dist/ui/interrupt.d.ts +4 -1
- package/dist/ui/interrupt.js +59 -7
- package/dist/ui/keyboardGuard.d.ts +76 -0
- package/dist/ui/keyboardGuard.js +94 -0
- package/dist/ui/loopPrompt.d.ts +17 -0
- package/dist/ui/loopPrompt.js +59 -0
- package/dist/ui/pegados.d.ts +50 -0
- package/dist/ui/pegados.js +83 -0
- package/dist/ui/permissionPrompt.js +84 -2
- package/dist/ui/prompt.d.ts +40 -0
- package/dist/ui/prompt.js +436 -131
- package/dist/ui/reasoningStream.d.ts +8 -0
- package/dist/ui/reasoningStream.js +99 -7
- package/dist/ui/salida.d.ts +10 -0
- package/dist/ui/salida.js +50 -0
- package/extension/README.md +95 -74
- package/extension/background.js +772 -181
- package/extension/content.js +527 -46
- package/extension/fuentes/Chocolatito-Marca.ttf +0 -0
- package/extension/iconos/128.png +0 -0
- package/extension/iconos/16.png +0 -0
- package/extension/iconos/32.png +0 -0
- package/extension/iconos/48.png +0 -0
- package/extension/iconos/logotipo.png +0 -0
- package/extension/manifest.json +28 -4
- package/extension/popup.html +16 -6
- package/extension/popup.js +28 -12
- package/package.json +3 -2
package/dist/config/license.js
CHANGED
|
@@ -47,13 +47,25 @@ function readRc() {
|
|
|
47
47
|
return {};
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Devuelve null si se guardo, o el motivo del fallo si no.
|
|
52
|
+
*
|
|
53
|
+
* Antes se tragaba la excepcion entera. Con el perfil del usuario en un recurso
|
|
54
|
+
* de solo lectura, el archivo sin permisos o el disco lleno, activar decia
|
|
55
|
+
* "Licencia activada" y no guardaba nada: el arranque siguiente volvia a pedir
|
|
56
|
+
* la clave y consumia otra de las 3 activaciones del cliente, hasta bloquearle
|
|
57
|
+
* una licencia que era suya.
|
|
58
|
+
*/
|
|
50
59
|
function writeRc(patch) {
|
|
51
60
|
try {
|
|
52
61
|
const p = getGlobalConfigPath();
|
|
53
62
|
const actual = readRc();
|
|
54
63
|
fs.writeFileSync(p, JSON.stringify({ ...actual, ...patch }, null, 2), "utf-8");
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
return err?.code || err?.message || String(err);
|
|
55
68
|
}
|
|
56
|
-
catch { }
|
|
57
69
|
}
|
|
58
70
|
export function getStoredLicense() {
|
|
59
71
|
return readRc().license || null;
|
|
@@ -63,6 +75,30 @@ function nombreDeMaquina() {
|
|
|
63
75
|
const host = os.hostname() || "equipo";
|
|
64
76
|
return `${host} (${os.platform()})`;
|
|
65
77
|
}
|
|
78
|
+
/**
|
|
79
|
+
* Codigos que significan "no he podido preguntar", no "te digo que no".
|
|
80
|
+
*
|
|
81
|
+
* 429 es el limite de peticiones documentado de Lemon Squeezy: basta con que
|
|
82
|
+
* dos equipos del mismo cliente arranquen a la vez. 408, 409, 425 y todo 5xx
|
|
83
|
+
* son caidas o reintentos. Ninguno opina sobre la licencia de nadie.
|
|
84
|
+
*/
|
|
85
|
+
function esCaidaTemporal(status) {
|
|
86
|
+
return status === 408 || status === 409 || status === 425 || status === 429 || status >= 500;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Solo la API de licencias contesta con estos campos.
|
|
90
|
+
*
|
|
91
|
+
* Un {"error":"Not Found"} de un cambio de ruta, o el {"message":"Server
|
|
92
|
+
* Error"} de un 500, son JSON perfectamente validos y parsean igual de bien,
|
|
93
|
+
* pero no son un veredicto sobre ninguna clave.
|
|
94
|
+
*/
|
|
95
|
+
function esRespuestaDeLicencia(data) {
|
|
96
|
+
if (!data || typeof data !== "object")
|
|
97
|
+
return false;
|
|
98
|
+
return (typeof data.activated === "boolean" ||
|
|
99
|
+
typeof data.valid === "boolean" ||
|
|
100
|
+
typeof data.deactivated === "boolean");
|
|
101
|
+
}
|
|
66
102
|
async function post(endpoint, params) {
|
|
67
103
|
const res = await fetch(`${API}/${endpoint}`, {
|
|
68
104
|
method: "POST",
|
|
@@ -73,8 +109,33 @@ async function post(endpoint, params) {
|
|
|
73
109
|
body: new URLSearchParams(params).toString(),
|
|
74
110
|
signal: AbortSignal.timeout(15_000),
|
|
75
111
|
});
|
|
76
|
-
//
|
|
77
|
-
|
|
112
|
+
// Aqui estaba la mitad que faltaba del punto 2 de arriba: se hacia res.json()
|
|
113
|
+
// sin mirar NUNCA el codigo. Solo contaba como "no he podido preguntar" que
|
|
114
|
+
// fetch lanzase. Una respuesta de error CON cuerpo JSON —el 429 del limite de
|
|
115
|
+
// peticiones, un 500 de una caida, un 404 por cambio de ruta— llegaba abajo
|
|
116
|
+
// como valid/activated ausentes, o sea como "la licencia no vale", y echaba
|
|
117
|
+
// al cliente que habia pagado sin darle el margen de 7 dias.
|
|
118
|
+
if (esCaidaTemporal(res.status)) {
|
|
119
|
+
throw new Error(res.status === 429
|
|
120
|
+
? "Lemon Squeezy esta limitando las peticiones (HTTP 429)"
|
|
121
|
+
: `Lemon Squeezy no responde bien (HTTP ${res.status})`);
|
|
122
|
+
}
|
|
123
|
+
let data;
|
|
124
|
+
try {
|
|
125
|
+
data = await res.json();
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
// Pasa con las paginas HTML que mete un balanceador delante de la API.
|
|
129
|
+
throw new Error(`Respuesta ilegible de Lemon Squeezy (HTTP ${res.status})`);
|
|
130
|
+
}
|
|
131
|
+
// 400 y 404 SI traen el motivo en el cuerpo cuando hablan de la clave, y esos
|
|
132
|
+
// se dejan pasar para que traducir() los explique: siempre vienen con su
|
|
133
|
+
// activated/valid/deactivated. Un 4xx que no trae ninguno de los tres no esta
|
|
134
|
+
// hablando de la licencia, asi que tampoco puede condenarla.
|
|
135
|
+
if (res.status >= 400 && !esRespuestaDeLicencia(data)) {
|
|
136
|
+
throw new Error(`Lemon Squeezy respondio HTTP ${res.status} sin decir nada de la clave`);
|
|
137
|
+
}
|
|
138
|
+
return data;
|
|
78
139
|
}
|
|
79
140
|
/** La clave tiene que ser de NUESTRA tienda, no de una cualquiera. */
|
|
80
141
|
function esDeNuestraTienda(data) {
|
|
@@ -112,11 +173,28 @@ export async function activate(licenseKey) {
|
|
|
112
173
|
// Chocolatito y el cliente no necesita clave de DeepSeek. Sin escribir esto,
|
|
113
174
|
// el CLI seguiria apuntando a api.deepseek.com y pidiendole al suscriptor una
|
|
114
175
|
// clave que no tiene por que tener.
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
176
|
+
//
|
|
177
|
+
// Se escribe SIEMPRE. Antes se respetaba un managed:false previo, y el unico
|
|
178
|
+
// sitio de todo el producto que escribe managed:false es deactivate(): quien
|
|
179
|
+
// liberaba el equipo y volvia a activar la misma clave aqui mismo se quedaba
|
|
180
|
+
// fuera del modo suscripcion, con el CLI pidiendole una clave de DeepSeek que
|
|
181
|
+
// no tiene o, peor, gastando la suya si la tenia guardada de antes.
|
|
182
|
+
const fallo = writeRc({ license, managed: true });
|
|
183
|
+
if (fallo) {
|
|
184
|
+
// La activacion ya esta consumida en Lemon Squeezy y no hay donde guardarla,
|
|
185
|
+
// asi que el arranque siguiente pediria la clave otra vez: tres vueltas y el
|
|
186
|
+
// cliente se queda bloqueado con una clave suya. Se devuelve la activacion
|
|
187
|
+
// antes de contarlo, para que reintentar no cueste nada.
|
|
188
|
+
try {
|
|
189
|
+
await post("deactivate", { license_key: license.key, instance_id: license.instanceId });
|
|
190
|
+
}
|
|
191
|
+
catch { }
|
|
192
|
+
return {
|
|
193
|
+
kind: "unpersisted",
|
|
194
|
+
reason: `Tu licencia es valida, pero no se pudo guardar en ${getGlobalConfigPath()} (${fallo}). ` +
|
|
195
|
+
"Arregla los permisos de ese archivo y vuelve a intentarlo.",
|
|
196
|
+
};
|
|
197
|
+
}
|
|
120
198
|
return { kind: "ok", license };
|
|
121
199
|
}
|
|
122
200
|
export async function validate(license) {
|
|
@@ -142,6 +220,9 @@ export async function validate(license) {
|
|
|
142
220
|
customerEmail: data?.meta?.customer_email || license.customerEmail,
|
|
143
221
|
productName: data?.meta?.product_name || license.productName,
|
|
144
222
|
};
|
|
223
|
+
// Si aqui no se puede escribir no se corta nada: la licencia ya esta en el
|
|
224
|
+
// archivo, solo se queda sin refrescar la fecha y se volvera a preguntar en el
|
|
225
|
+
// arranque siguiente. No cuesta activaciones, al contrario que en activate().
|
|
145
226
|
writeRc({ license: actualizada });
|
|
146
227
|
return { kind: "ok", license: actualizada };
|
|
147
228
|
}
|
|
@@ -158,7 +239,18 @@ export async function deactivate() {
|
|
|
158
239
|
if (!data?.deactivated) {
|
|
159
240
|
return { ok: false, message: traducir(data?.error) };
|
|
160
241
|
}
|
|
161
|
-
writeRc({ license: undefined, managed: false });
|
|
242
|
+
const fallo = writeRc({ license: undefined, managed: false });
|
|
243
|
+
if (fallo) {
|
|
244
|
+
// Lemon Squeezy ya ha borrado la instancia. Si encima decimos que todo fue
|
|
245
|
+
// bien, la licencia se queda escrita en el archivo y el arranque siguiente
|
|
246
|
+
// valida una activacion que ya no existe: "esta maquina ya no figura como
|
|
247
|
+
// activada" sin que el usuario haya hecho nada raro.
|
|
248
|
+
return {
|
|
249
|
+
ok: false,
|
|
250
|
+
message: `Se libero el equipo en Lemon Squeezy, pero no se pudo actualizar ${getGlobalConfigPath()} (${fallo}): ` +
|
|
251
|
+
"la licencia sigue escrita ahi. Borra ese archivo a mano o el proximo arranque fallara al comprobarla.",
|
|
252
|
+
};
|
|
253
|
+
}
|
|
162
254
|
return {
|
|
163
255
|
ok: true,
|
|
164
256
|
message: "Licencia liberada en este equipo. Ya puedes activarla en otro.",
|
|
@@ -256,6 +348,12 @@ export async function ensureLicense() {
|
|
|
256
348
|
console.log(chalk.red(`\n✖ ${res.reason}\n`));
|
|
257
349
|
return false;
|
|
258
350
|
}
|
|
351
|
+
if (res.kind === "unpersisted") {
|
|
352
|
+
// Decir "✔ Licencia activada" aqui era mentira con consecuencias: sin
|
|
353
|
+
// guardar nada, cada arranque volvia a activar y se comia una activacion.
|
|
354
|
+
console.log(chalk.red(`\n✖ ${res.reason}\n`));
|
|
355
|
+
return false;
|
|
356
|
+
}
|
|
259
357
|
console.log(chalk.red(`\n✖ No se pudo contactar con Lemon Squeezy para activar (${res.reason}).`) +
|
|
260
358
|
chalk.gray("\n Comprueba tu conexión e inténtalo otra vez.\n"));
|
|
261
359
|
return false;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Presupuestos de ejecucion.
|
|
3
|
+
*
|
|
4
|
+
* Estaban escritos en el codigo: 30 iteraciones en el bucle principal y 14 en
|
|
5
|
+
* los subagentes. Son numeros razonables para la tarea media y equivocados en
|
|
6
|
+
* los dos extremos: en una refactorizacion grande el turno se corta a medias
|
|
7
|
+
* ("se agoto el limite de 30 iteraciones") y hay que repetir la peticion para
|
|
8
|
+
* que siga; en un guion desatendido, 30 vueltas de un agente perdido son 30
|
|
9
|
+
* llamadas pagadas antes de que nadie mire.
|
|
10
|
+
*
|
|
11
|
+
* Quien conoce su tarea y su presupuesto es el usuario, asi que se saca a
|
|
12
|
+
* configuracion. Con topes: un limite de 500 iteraciones no es una eleccion
|
|
13
|
+
* informada, es un cero de mas.
|
|
14
|
+
*/
|
|
15
|
+
export interface Limites {
|
|
16
|
+
/** Vueltas del bucle principal en un turno. */
|
|
17
|
+
iteracionesPorTurno: number;
|
|
18
|
+
/** Vueltas de un subagente. */
|
|
19
|
+
iteracionesDeSubagente: number;
|
|
20
|
+
}
|
|
21
|
+
export declare const LIMITES_POR_DEFECTO: Limites;
|
|
22
|
+
/**
|
|
23
|
+
* Lee los limites de la configuracion. El proyecto puede bajarlos o subirlos:
|
|
24
|
+
* a diferencia de los permisos, aqui no hay nada que elevar —un limite mas
|
|
25
|
+
* alto no da acceso a nada, solo gasta mas cuota del propio usuario, y el corte
|
|
26
|
+
* duro sigue siendo el tope de 200.
|
|
27
|
+
*/
|
|
28
|
+
export declare function leerLimites(cwd?: string): Limites;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
export const LIMITES_POR_DEFECTO = {
|
|
5
|
+
iteracionesPorTurno: 30,
|
|
6
|
+
iteracionesDeSubagente: 14,
|
|
7
|
+
};
|
|
8
|
+
const MINIMO = 3;
|
|
9
|
+
const MAXIMO = 200;
|
|
10
|
+
function enRango(valor, porDefecto) {
|
|
11
|
+
const n = typeof valor === "string" ? Number(valor) : valor;
|
|
12
|
+
if (typeof n !== "number" || !Number.isFinite(n))
|
|
13
|
+
return porDefecto;
|
|
14
|
+
return Math.max(MINIMO, Math.min(MAXIMO, Math.round(n)));
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Lee los limites de la configuracion. El proyecto puede bajarlos o subirlos:
|
|
18
|
+
* a diferencia de los permisos, aqui no hay nada que elevar —un limite mas
|
|
19
|
+
* alto no da acceso a nada, solo gasta mas cuota del propio usuario, y el corte
|
|
20
|
+
* duro sigue siendo el tope de 200.
|
|
21
|
+
*/
|
|
22
|
+
export function leerLimites(cwd = process.cwd()) {
|
|
23
|
+
const limites = { ...LIMITES_POR_DEFECTO };
|
|
24
|
+
for (const ruta of [
|
|
25
|
+
path.join(os.homedir(), ".chocolatito", "settings.json"),
|
|
26
|
+
path.join(cwd, ".chocolatito", "settings.json"),
|
|
27
|
+
]) {
|
|
28
|
+
try {
|
|
29
|
+
if (!fs.existsSync(ruta))
|
|
30
|
+
continue;
|
|
31
|
+
const parsed = JSON.parse(fs.readFileSync(ruta, "utf-8"));
|
|
32
|
+
const l = parsed?.limits;
|
|
33
|
+
if (!l)
|
|
34
|
+
continue;
|
|
35
|
+
if (l.maxIterations !== undefined) {
|
|
36
|
+
limites.iteracionesPorTurno = enRango(l.maxIterations, limites.iteracionesPorTurno);
|
|
37
|
+
}
|
|
38
|
+
if (l.subagentIterations !== undefined) {
|
|
39
|
+
limites.iteracionesDeSubagente = enRango(l.subagentIterations, limites.iteracionesDeSubagente);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
catch { }
|
|
43
|
+
}
|
|
44
|
+
if (process.env.CHOCOLATITO_MAX_ITERATIONS) {
|
|
45
|
+
limites.iteracionesPorTurno = enRango(process.env.CHOCOLATITO_MAX_ITERATIONS, limites.iteracionesPorTurno);
|
|
46
|
+
}
|
|
47
|
+
if (process.env.CHOCOLATITO_SUBAGENT_ITERATIONS) {
|
|
48
|
+
limites.iteracionesDeSubagente = enRango(process.env.CHOCOLATITO_SUBAGENT_ITERATIONS, limites.iteracionesDeSubagente);
|
|
49
|
+
}
|
|
50
|
+
return limites;
|
|
51
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Un Node viejo se dice claro, no se descubre a base de errores raros.
|
|
3
|
+
*
|
|
4
|
+
* `engines` en el package.json solo hace que npm suelte un aviso que nadie lee:
|
|
5
|
+
* la instalacion termina bien y el fallo aparece despues, al arrancar, con un
|
|
6
|
+
* "fetch is not defined" o un error de sintaxis dentro de una dependencia. El
|
|
7
|
+
* que acaba de pagar da por hecho que el producto esta roto, y no le falta
|
|
8
|
+
* razon desde donde el lo ve.
|
|
9
|
+
*
|
|
10
|
+
* Se comprueba antes que nada: antes de la licencia, antes de leer la
|
|
11
|
+
* configuracion y antes de importar nada que pueda reventar por esto mismo.
|
|
12
|
+
*/
|
|
13
|
+
export declare function comprobarNode(version?: string): string | null;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** La version de Node mas antigua con la que esto funciona. */
|
|
2
|
+
const NODE_MINIMO = 20;
|
|
3
|
+
/**
|
|
4
|
+
* Un Node viejo se dice claro, no se descubre a base de errores raros.
|
|
5
|
+
*
|
|
6
|
+
* `engines` en el package.json solo hace que npm suelte un aviso que nadie lee:
|
|
7
|
+
* la instalacion termina bien y el fallo aparece despues, al arrancar, con un
|
|
8
|
+
* "fetch is not defined" o un error de sintaxis dentro de una dependencia. El
|
|
9
|
+
* que acaba de pagar da por hecho que el producto esta roto, y no le falta
|
|
10
|
+
* razon desde donde el lo ve.
|
|
11
|
+
*
|
|
12
|
+
* Se comprueba antes que nada: antes de la licencia, antes de leer la
|
|
13
|
+
* configuracion y antes de importar nada que pueda reventar por esto mismo.
|
|
14
|
+
*/
|
|
15
|
+
export function comprobarNode(version = process.version) {
|
|
16
|
+
const mayor = Number.parseInt(String(version).replace(/^v/, "").split(".")[0], 10);
|
|
17
|
+
// Si no se entiende la version, no es motivo para no dejar trabajar a nadie.
|
|
18
|
+
if (!Number.isFinite(mayor))
|
|
19
|
+
return null;
|
|
20
|
+
if (mayor >= NODE_MINIMO)
|
|
21
|
+
return null;
|
|
22
|
+
return (`Chocolatito Code necesita Node ${NODE_MINIMO} o superior, y estas con ${version}.\n\n` +
|
|
23
|
+
` Descargalo en https://nodejs.org (la version LTS vale).\n` +
|
|
24
|
+
` Despues, vuelve a instalarlo: npm i -g chocolatito-code\n\n` +
|
|
25
|
+
`No es un problema de tu licencia ni de tu conexion.`);
|
|
26
|
+
}
|
|
@@ -6,18 +6,60 @@ export interface SettingsConfig {
|
|
|
6
6
|
denyPatterns?: string[];
|
|
7
7
|
};
|
|
8
8
|
}
|
|
9
|
+
/**
|
|
10
|
+
* Una ruta esta fuera del proyecto si, resuelta, no cuelga de la raiz.
|
|
11
|
+
*
|
|
12
|
+
* Vale para rutas absolutas (C:\Windows\..., ~/.ssh/...) y para las relativas
|
|
13
|
+
* con ".." que salen del arbol, que es la forma que no se ve venir leyendo la
|
|
14
|
+
* llamada por encima. path.relative en Windows ya compara sin distinguir
|
|
15
|
+
* mayusculas y devuelve la ruta entera cuando cambia de unidad.
|
|
16
|
+
*/
|
|
17
|
+
export declare function isOutsideProject(projectRoot: string, target: unknown): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* create_image mete dos cosas muy distintas en el mismo argumento: un SVG, que
|
|
20
|
+
* solo se escribe en disco, o codigo Python, que se guarda en un .py y se lanza
|
|
21
|
+
* con `python`. Quien lo distingue es esta funcion, y la usan tanto la
|
|
22
|
+
* herramienta como la tabla de permisos: si cada una detectara el SVG a su
|
|
23
|
+
* manera, bastaria una diferencia entre las dos para colar codigo por la rama
|
|
24
|
+
* que no pregunta.
|
|
25
|
+
*/
|
|
26
|
+
export declare function isSvgSource(script: unknown): boolean;
|
|
9
27
|
/**
|
|
10
28
|
* Detecta comandos destructivos o de alto riesgo que siempre deben pedir confirmación.
|
|
11
29
|
* Comandos seguros de desarrollo (python, npm, pytest, tsc, git status, etc.) no interrumpen al usuario.
|
|
12
30
|
*/
|
|
13
31
|
export declare function isDangerousCommand(command: string): boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Firma con la que se recuerda un comando aprobado.
|
|
34
|
+
*
|
|
35
|
+
* El bucle guardaba command.split(" ")[0], o sea solo el binario: aprobar un
|
|
36
|
+
* "git status" dejaba escrito el patron "git" y, como el allow autoriza por
|
|
37
|
+
* prefijo, a partir de ahi pasaban sin dialogo tambien "git push --force" y
|
|
38
|
+
* "git reset --hard", en todas las sesiones del proyecto.
|
|
39
|
+
*
|
|
40
|
+
* La firma es binario + subcomando ("git status", "npm run build"). Si el
|
|
41
|
+
* comando no trae subcomando sino opciones se guarda entero: ensancharlo hasta
|
|
42
|
+
* el binario es justo el fallo que se esta arreglando.
|
|
43
|
+
*/
|
|
44
|
+
export declare function commandSignature(command: string): string;
|
|
14
45
|
export declare class PermissionManager {
|
|
15
46
|
mode: PermissionMode;
|
|
16
47
|
allowPatterns: Set<string>;
|
|
17
48
|
denyPatterns: Set<string>;
|
|
18
49
|
private settingsFilePath;
|
|
50
|
+
/** Raiz del proyecto: el limite dentro del cual escribir no interrumpe. */
|
|
51
|
+
projectRoot: string;
|
|
19
52
|
constructor(initialMode?: PermissionMode, cwd?: string);
|
|
20
53
|
loadSettings(): void;
|
|
54
|
+
/**
|
|
55
|
+
* Aplica una capa de settings.
|
|
56
|
+
*
|
|
57
|
+
* La capa del proyecto solo puede RESTRINGIR. Antes se aplicaba despues de la
|
|
58
|
+
* global y fijaba el modo sin mas, asi que un repositorio clonado con
|
|
59
|
+
* {"permissions":{"mode":"bypass"}} en su .chocolatito/settings.json apagaba
|
|
60
|
+
* los dialogos en la maquina de quien lo abriera, sin decir nada. Elevar
|
|
61
|
+
* permisos es cosa del usuario: su settings global o la linea de comandos.
|
|
62
|
+
*/
|
|
21
63
|
private applySettings;
|
|
22
64
|
saveAllowPattern(pattern: string): void;
|
|
23
65
|
setMode(mode: PermissionMode): void;
|
|
@@ -27,7 +69,36 @@ export declare class PermissionManager {
|
|
|
27
69
|
* Modo plan = SOLO LECTURA de verdad.
|
|
28
70
|
*/
|
|
29
71
|
isBlockedInPlanMode(toolName: string, args: Record<string, any>): string | null;
|
|
30
|
-
|
|
72
|
+
/**
|
|
73
|
+
* `cwd` es contra que directorio se mide "dentro del proyecto". Tiene que ser
|
|
74
|
+
* el MISMO que recibe executeToolCall, porque es contra el que las
|
|
75
|
+
* herramientas resuelven las rutas relativas: si aqui se midiera contra la
|
|
76
|
+
* raiz de la sesion y el bucle escribiera desde otro directorio (currentCwd
|
|
77
|
+
* cambia con cd), un "x.txt" saldria "dentro" en la comprobacion y caeria
|
|
78
|
+
* fuera al escribirse. Sin argumento se usa la raiz con la que se construyo.
|
|
79
|
+
*/
|
|
80
|
+
requiresConfirmation(toolName: string, args: Record<string, any>, cwd?: string): boolean;
|
|
81
|
+
/**
|
|
82
|
+
* Rutas de la llamada que caen fuera de la raiz del proyecto. Vacio si todo
|
|
83
|
+
* queda dentro (o si la herramienta no escribe en disco: leer fuera del
|
|
84
|
+
* proyecto es legitimo y no se pregunta).
|
|
85
|
+
*/
|
|
86
|
+
rutasFueraDelProyecto(toolName: string, args: Record<string, any>, cwd?: string): string[];
|
|
87
|
+
/**
|
|
88
|
+
* Patron denegado: devuelve cual, o null. Corta la llamada entera; no es un
|
|
89
|
+
* "pregunta antes".
|
|
90
|
+
*
|
|
91
|
+
* denyPatterns se leia del settings.json y se volvia a guardar tal cual, pero
|
|
92
|
+
* NADIE lo consultaba: la unica lista que se miraba era la de allow. Es decir,
|
|
93
|
+
* prohibir algo por configuracion no hacia absolutamente nada.
|
|
94
|
+
*
|
|
95
|
+
* La coincidencia es la misma que la del allow (firma exacta o prefijo de
|
|
96
|
+
* comando) con una diferencia deliberada: un allow no puede autorizar lo que
|
|
97
|
+
* venga encadenado detras, pero un deny si tiene que alcanzarlo. Si no,
|
|
98
|
+
* "echo hola && git push --force" se saltaria un deny de "git push" con solo
|
|
99
|
+
* poner algo inocente delante.
|
|
100
|
+
*/
|
|
101
|
+
deniedBy(toolName: string, args: Record<string, any>): string | null;
|
|
31
102
|
/**
|
|
32
103
|
* Un patron autorizado vale para ESE comando, no para cualquiera que lo
|
|
33
104
|
* contenga. Con la comparacion por subcadena de antes, autorizar "git status"
|
|
@@ -35,4 +106,8 @@ export declare class PermissionManager {
|
|
|
35
106
|
* apareciera en algun sitio de la linea.
|
|
36
107
|
*/
|
|
37
108
|
private isPatternAllowed;
|
|
109
|
+
/** Firma exacta de la llamada ("run_command:npm test") o ruta/accion exacta. */
|
|
110
|
+
private coincideExacta;
|
|
111
|
+
/** Prefijo de comando ("npm", "git status"), solo al principio de la linea. */
|
|
112
|
+
private coincidePrefijo;
|
|
38
113
|
}
|