chocolatito-code 1.0.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.
Files changed (140) hide show
  1. package/.mcp.json.ejemplo +19 -0
  2. package/COMPUTER-USE.md +484 -0
  3. package/LICENSE +21 -0
  4. package/README.md +186 -0
  5. package/dist/agent/context.d.ts +9 -0
  6. package/dist/agent/context.js +123 -0
  7. package/dist/agent/fileTracker.d.ts +16 -0
  8. package/dist/agent/fileTracker.js +53 -0
  9. package/dist/agent/loop.d.ts +25 -0
  10. package/dist/agent/loop.js +524 -0
  11. package/dist/agent/mentions.d.ts +11 -0
  12. package/dist/agent/mentions.js +43 -0
  13. package/dist/agent/retry.d.ts +17 -0
  14. package/dist/agent/retry.js +57 -0
  15. package/dist/agent/subagent.d.ts +2 -0
  16. package/dist/agent/subagent.js +116 -0
  17. package/dist/agent/syntaxValidator.d.ts +5 -0
  18. package/dist/agent/syntaxValidator.js +71 -0
  19. package/dist/agent/tracker.d.ts +19 -0
  20. package/dist/agent/tracker.js +38 -0
  21. package/dist/agent/undoManager.d.ts +37 -0
  22. package/dist/agent/undoManager.js +95 -0
  23. package/dist/config/constants.d.ts +77 -0
  24. package/dist/config/constants.js +146 -0
  25. package/dist/config/engine.d.ts +49 -0
  26. package/dist/config/engine.js +86 -0
  27. package/dist/config/env.d.ts +13 -0
  28. package/dist/config/env.js +103 -0
  29. package/dist/config/license.d.ts +39 -0
  30. package/dist/config/license.js +306 -0
  31. package/dist/config/permissions.d.ts +38 -0
  32. package/dist/config/permissions.js +239 -0
  33. package/dist/config/quota.d.ts +58 -0
  34. package/dist/config/quota.js +156 -0
  35. package/dist/hooks/manager.d.ts +60 -0
  36. package/dist/hooks/manager.js +193 -0
  37. package/dist/index.d.ts +2 -0
  38. package/dist/index.js +470 -0
  39. package/dist/mcp/client.d.ts +55 -0
  40. package/dist/mcp/client.js +282 -0
  41. package/dist/mcp/manager.d.ts +26 -0
  42. package/dist/mcp/manager.js +167 -0
  43. package/dist/memory/manager.d.ts +9 -0
  44. package/dist/memory/manager.js +59 -0
  45. package/dist/prompts/systemPrompt.d.ts +5 -0
  46. package/dist/prompts/systemPrompt.js +177 -0
  47. package/dist/sessions/manager.d.ts +21 -0
  48. package/dist/sessions/manager.js +54 -0
  49. package/dist/skills/manager.d.ts +14 -0
  50. package/dist/skills/manager.js +86 -0
  51. package/dist/tools/browserCdp.d.ts +26 -0
  52. package/dist/tools/browserCdp.js +610 -0
  53. package/dist/tools/browserExtension.d.ts +58 -0
  54. package/dist/tools/browserExtension.js +479 -0
  55. package/dist/tools/browserLive.d.ts +12 -0
  56. package/dist/tools/browserLive.js +265 -0
  57. package/dist/tools/changeDir.d.ts +5 -0
  58. package/dist/tools/changeDir.js +63 -0
  59. package/dist/tools/computerUse.d.ts +58 -0
  60. package/dist/tools/computerUse.js +567 -0
  61. package/dist/tools/createImage.d.ts +1 -0
  62. package/dist/tools/createImage.js +34 -0
  63. package/dist/tools/definitions.d.ts +8 -0
  64. package/dist/tools/definitions.js +401 -0
  65. package/dist/tools/deleteFile.d.ts +1 -0
  66. package/dist/tools/deleteFile.js +17 -0
  67. package/dist/tools/editFile.d.ts +5 -0
  68. package/dist/tools/editFile.js +88 -0
  69. package/dist/tools/findFiles.d.ts +1 -0
  70. package/dist/tools/findFiles.js +33 -0
  71. package/dist/tools/getSystemInfo.d.ts +1 -0
  72. package/dist/tools/getSystemInfo.js +23 -0
  73. package/dist/tools/gitTools.d.ts +3 -0
  74. package/dist/tools/gitTools.js +47 -0
  75. package/dist/tools/grepSearch.d.ts +1 -0
  76. package/dist/tools/grepSearch.js +37 -0
  77. package/dist/tools/listDir.d.ts +1 -0
  78. package/dist/tools/listDir.js +33 -0
  79. package/dist/tools/moveCopyFile.d.ts +1 -0
  80. package/dist/tools/moveCopyFile.js +21 -0
  81. package/dist/tools/runCommand.d.ts +1 -0
  82. package/dist/tools/runCommand.js +61 -0
  83. package/dist/tools/runner.d.ts +5 -0
  84. package/dist/tools/runner.js +177 -0
  85. package/dist/tools/safety.d.ts +17 -0
  86. package/dist/tools/safety.js +33 -0
  87. package/dist/tools/todoTool.d.ts +8 -0
  88. package/dist/tools/todoTool.js +27 -0
  89. package/dist/tools/toolDefsComputer.d.ts +13 -0
  90. package/dist/tools/toolDefsComputer.js +205 -0
  91. package/dist/tools/truncator.d.ts +11 -0
  92. package/dist/tools/truncator.js +24 -0
  93. package/dist/tools/viewFile.d.ts +1 -0
  94. package/dist/tools/viewFile.js +45 -0
  95. package/dist/tools/visionBridge.d.ts +37 -0
  96. package/dist/tools/visionBridge.js +107 -0
  97. package/dist/tools/webFetch.d.ts +1 -0
  98. package/dist/tools/webFetch.js +25 -0
  99. package/dist/tools/webSearch.d.ts +1 -0
  100. package/dist/tools/webSearch.js +35 -0
  101. package/dist/tools/win/hostClient.d.ts +31 -0
  102. package/dist/tools/win/hostClient.js +158 -0
  103. package/dist/tools/win/hostScript.d.ts +1 -0
  104. package/dist/tools/win/hostScript.js +925 -0
  105. package/dist/tools/writeFile.d.ts +1 -0
  106. package/dist/tools/writeFile.js +13 -0
  107. package/dist/ui/banner.d.ts +10 -0
  108. package/dist/ui/banner.js +165 -0
  109. package/dist/ui/diffViewer.d.ts +1 -0
  110. package/dist/ui/diffViewer.js +60 -0
  111. package/dist/ui/interrupt.d.ts +17 -0
  112. package/dist/ui/interrupt.js +156 -0
  113. package/dist/ui/markdownStream.d.ts +23 -0
  114. package/dist/ui/markdownStream.js +0 -0
  115. package/dist/ui/modes.d.ts +28 -0
  116. package/dist/ui/modes.js +57 -0
  117. package/dist/ui/permissionPrompt.d.ts +35 -0
  118. package/dist/ui/permissionPrompt.js +271 -0
  119. package/dist/ui/prompt.d.ts +7 -0
  120. package/dist/ui/prompt.js +466 -0
  121. package/dist/ui/reasoningStream.d.ts +6 -0
  122. package/dist/ui/reasoningStream.js +38 -0
  123. package/dist/ui/renderer.d.ts +10 -0
  124. package/dist/ui/renderer.js +142 -0
  125. package/dist/ui/spinner.d.ts +24 -0
  126. package/dist/ui/spinner.js +111 -0
  127. package/dist/ui/theme.d.ts +36 -0
  128. package/dist/ui/theme.js +37 -0
  129. package/dist/ui/typeAhead.d.ts +31 -0
  130. package/dist/ui/typeAhead.js +87 -0
  131. package/extension/README.md +74 -0
  132. package/extension/background.js +575 -0
  133. package/extension/content.js +446 -0
  134. package/extension/manifest.json +36 -0
  135. package/extension/popup.html +22 -0
  136. package/extension/popup.js +12 -0
  137. package/package.json +66 -0
  138. package/skills/complex-refactor/SKILL.md +13 -0
  139. package/skills/deep-debugger/SKILL.md +14 -0
  140. package/skills/software-architect/SKILL.md +20 -0
@@ -0,0 +1,55 @@
1
+ export interface McpTool {
2
+ name: string;
3
+ description?: string;
4
+ inputSchema?: Record<string, any>;
5
+ }
6
+ export interface McpServerConfig {
7
+ type?: "stdio" | "http" | "sse" | "streamable-http";
8
+ command?: string;
9
+ args?: string[];
10
+ env?: Record<string, string>;
11
+ url?: string;
12
+ headers?: Record<string, string>;
13
+ timeout?: number;
14
+ }
15
+ /**
16
+ * Expande ${VAR} y ${VAR:-porDefecto} igual que hace Claude Code, para poder
17
+ * guardar la configuracion en el repositorio sin meter secretos dentro.
18
+ */
19
+ export declare function expandVars(value: string): string;
20
+ export declare class McpClient {
21
+ readonly name: string;
22
+ tools: McpTool[];
23
+ lastError: string | null;
24
+ private config;
25
+ private proc;
26
+ private nextId;
27
+ private pending;
28
+ private ready;
29
+ private stderrTail;
30
+ constructor(name: string, config: McpServerConfig);
31
+ get transport(): string;
32
+ get connected(): boolean;
33
+ connect(timeoutMs?: number): Promise<boolean>;
34
+ /**
35
+ * Lanzamiento del servidor en Windows.
36
+ *
37
+ * npx, npm, uvx y companyia son scripts .cmd. Node se niega a ejecutarlos
38
+ * directamente (spawn EINVAL) desde el parche de seguridad de 2024, y la
39
+ * alternativa comoda, shell:true, concatena los argumentos SIN escaparlos:
40
+ * un argumento con comillas o & se vuelve inyeccion de comandos.
41
+ *
42
+ * La via segura es invocar cmd.exe con los argumentos como lista: Node los
43
+ * entrecomilla uno a uno en vez de pegarlos en una cadena.
44
+ */
45
+ private launchArgs;
46
+ private spawnServer;
47
+ private onMessage;
48
+ private request;
49
+ private notify;
50
+ private httpRequest;
51
+ private initialize;
52
+ private listTools;
53
+ callTool(toolName: string, args: Record<string, any>, timeoutMs?: number): Promise<string>;
54
+ disconnect(): void;
55
+ }
@@ -0,0 +1,282 @@
1
+ import { spawn } from "node:child_process";
2
+ import readline from "node:readline";
3
+ /**
4
+ * Cliente del Model Context Protocol.
5
+ *
6
+ * MCP es como Claude Code se conecta a herramientas externas: bases de datos,
7
+ * Figma, Notion, Slack, servidores propios... En vez de programar una
8
+ * integracion por servicio, se habla un protocolo comun y cualquier servidor MCP
9
+ * queda disponible como herramientas mas del agente.
10
+ *
11
+ * Transporte stdio: JSON-RPC 2.0 delimitado por saltos de linea sobre la entrada
12
+ * y salida estandar del proceso servidor.
13
+ */
14
+ const PROTOCOL_VERSION = "2025-06-18";
15
+ /**
16
+ * Expande ${VAR} y ${VAR:-porDefecto} igual que hace Claude Code, para poder
17
+ * guardar la configuracion en el repositorio sin meter secretos dentro.
18
+ */
19
+ export function expandVars(value) {
20
+ return value.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)(?::-([^}]*))?\}/g, (_m, name, fallback) => {
21
+ const v = process.env[name];
22
+ if (v !== undefined && v !== "")
23
+ return v;
24
+ return fallback ?? "";
25
+ });
26
+ }
27
+ function expandDeep(value) {
28
+ if (typeof value === "string")
29
+ return expandVars(value);
30
+ if (Array.isArray(value))
31
+ return value.map(expandDeep);
32
+ if (value && typeof value === "object") {
33
+ const out = {};
34
+ for (const [k, v] of Object.entries(value))
35
+ out[k] = expandDeep(v);
36
+ return out;
37
+ }
38
+ return value;
39
+ }
40
+ export class McpClient {
41
+ name;
42
+ tools = [];
43
+ lastError = null;
44
+ config;
45
+ proc = null;
46
+ nextId = 1;
47
+ pending = new Map();
48
+ ready = false;
49
+ stderrTail = "";
50
+ constructor(name, config) {
51
+ this.name = name;
52
+ this.config = expandDeep(config);
53
+ }
54
+ get transport() {
55
+ if (this.config.url)
56
+ return this.config.type || "http";
57
+ return "stdio";
58
+ }
59
+ get connected() {
60
+ return this.ready;
61
+ }
62
+ // ------------------------------------------------------------ conexion
63
+ async connect(timeoutMs = 20_000) {
64
+ try {
65
+ if (this.config.url) {
66
+ await this.initialize(timeoutMs);
67
+ }
68
+ else {
69
+ if (!this.config.command)
70
+ throw new Error("falta 'command' para un servidor stdio");
71
+ await this.spawnServer();
72
+ await this.initialize(timeoutMs);
73
+ }
74
+ this.tools = await this.listTools(timeoutMs);
75
+ this.ready = true;
76
+ return true;
77
+ }
78
+ catch (err) {
79
+ // El stderr del servidor suele explicar mucho mejor el fallo que el
80
+ // mensaje de la excepcion (falta un paquete, falta una API key...).
81
+ const extra = this.stderrTail.trim() ? ` — salida del servidor: ${this.stderrTail.trim().slice(-400)}` : "";
82
+ this.lastError = `${err?.message || String(err)}${extra}`;
83
+ this.disconnect();
84
+ return false;
85
+ }
86
+ }
87
+ /**
88
+ * Lanzamiento del servidor en Windows.
89
+ *
90
+ * npx, npm, uvx y companyia son scripts .cmd. Node se niega a ejecutarlos
91
+ * directamente (spawn EINVAL) desde el parche de seguridad de 2024, y la
92
+ * alternativa comoda, shell:true, concatena los argumentos SIN escaparlos:
93
+ * un argumento con comillas o & se vuelve inyeccion de comandos.
94
+ *
95
+ * La via segura es invocar cmd.exe con los argumentos como lista: Node los
96
+ * entrecomilla uno a uno en vez de pegarlos en una cadena.
97
+ */
98
+ launchArgs() {
99
+ const cmd = this.config.command;
100
+ const args = this.config.args || [];
101
+ if (process.platform !== "win32")
102
+ return { exe: cmd, args };
103
+ const esBinario = /\.exe$/i.test(cmd);
104
+ if (esBinario)
105
+ return { exe: cmd, args };
106
+ const comspec = process.env.ComSpec || "cmd.exe";
107
+ return { exe: comspec, args: ["/d", "/s", "/c", cmd, ...args] };
108
+ }
109
+ async spawnServer() {
110
+ const { exe, args } = this.launchArgs();
111
+ const proc = spawn(exe, args, {
112
+ env: { ...process.env, ...(this.config.env || {}) },
113
+ stdio: ["pipe", "pipe", "pipe"],
114
+ windowsHide: true,
115
+ shell: false,
116
+ });
117
+ this.proc = proc;
118
+ const rl = readline.createInterface({ input: proc.stdout });
119
+ rl.on("line", (line) => this.onMessage(line));
120
+ proc.stderr.on("data", (d) => {
121
+ this.stderrTail = (this.stderrTail + d.toString()).slice(-2000);
122
+ });
123
+ proc.on("exit", (code) => {
124
+ this.ready = false;
125
+ for (const [, p] of this.pending) {
126
+ clearTimeout(p.timer);
127
+ p.reject(new Error(`el servidor MCP termino (codigo ${code})`));
128
+ }
129
+ this.pending.clear();
130
+ });
131
+ proc.on("error", (err) => {
132
+ this.stderrTail +=
133
+ err && err.code === "ENOENT"
134
+ ? `\nNo se pudo lanzar "${this.config.command}". Comprueba que esta instalado y en el PATH.`
135
+ : `\n${err.message}`;
136
+ });
137
+ }
138
+ onMessage(line) {
139
+ const trimmed = line.trim();
140
+ if (!trimmed.startsWith("{"))
141
+ return;
142
+ let msg;
143
+ try {
144
+ msg = JSON.parse(trimmed);
145
+ }
146
+ catch {
147
+ return;
148
+ }
149
+ if (typeof msg.id !== "number")
150
+ return; // notificacion del servidor
151
+ const waiter = this.pending.get(msg.id);
152
+ if (!waiter)
153
+ return;
154
+ clearTimeout(waiter.timer);
155
+ this.pending.delete(msg.id);
156
+ if (msg.error)
157
+ waiter.reject(new Error(msg.error.message || JSON.stringify(msg.error)));
158
+ else
159
+ waiter.resolve(msg.result);
160
+ }
161
+ // ------------------------------------------------------------ JSON-RPC
162
+ async request(method, params, timeoutMs) {
163
+ if (this.config.url)
164
+ return this.httpRequest(method, params, timeoutMs);
165
+ if (!this.proc || this.proc.killed)
166
+ throw new Error("el servidor MCP no esta activo");
167
+ const id = this.nextId++;
168
+ const payload = JSON.stringify({ jsonrpc: "2.0", id, method, params }) + "\n";
169
+ return new Promise((resolve, reject) => {
170
+ const timer = setTimeout(() => {
171
+ this.pending.delete(id);
172
+ reject(new Error(`"${method}" no respondio en ${timeoutMs}ms`));
173
+ }, timeoutMs);
174
+ this.pending.set(id, { resolve, reject, timer });
175
+ try {
176
+ this.proc.stdin.write(payload);
177
+ }
178
+ catch (err) {
179
+ clearTimeout(timer);
180
+ this.pending.delete(id);
181
+ reject(err);
182
+ }
183
+ });
184
+ }
185
+ notify(method, params) {
186
+ if (this.config.url || !this.proc)
187
+ return;
188
+ try {
189
+ this.proc.stdin.write(JSON.stringify({ jsonrpc: "2.0", method, params }) + "\n");
190
+ }
191
+ catch { }
192
+ }
193
+ async httpRequest(method, params, timeoutMs) {
194
+ const controller = new AbortController();
195
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
196
+ try {
197
+ const res = await fetch(this.config.url, {
198
+ method: "POST",
199
+ headers: {
200
+ "Content-Type": "application/json",
201
+ Accept: "application/json, text/event-stream",
202
+ ...(this.config.headers || {}),
203
+ },
204
+ body: JSON.stringify({ jsonrpc: "2.0", id: this.nextId++, method, params }),
205
+ signal: controller.signal,
206
+ });
207
+ if (!res.ok)
208
+ throw new Error(`HTTP ${res.status}`);
209
+ const text = await res.text();
210
+ // El transporte HTTP puede responder JSON directo o un flujo SSE.
211
+ const body = text.includes("data:")
212
+ ? text
213
+ .split("\n")
214
+ .filter((l) => l.startsWith("data:"))
215
+ .map((l) => l.slice(5).trim())
216
+ .filter(Boolean)
217
+ .pop() || "{}"
218
+ : text;
219
+ const parsed = JSON.parse(body);
220
+ if (parsed.error)
221
+ throw new Error(parsed.error.message || JSON.stringify(parsed.error));
222
+ return parsed.result;
223
+ }
224
+ finally {
225
+ clearTimeout(timer);
226
+ }
227
+ }
228
+ // ------------------------------------------------------------ protocolo
229
+ async initialize(timeoutMs) {
230
+ await this.request("initialize", {
231
+ protocolVersion: PROTOCOL_VERSION,
232
+ capabilities: { tools: {} },
233
+ clientInfo: { name: "chocolatito-code", version: "1.0.0" },
234
+ }, timeoutMs);
235
+ this.notify("notifications/initialized", {});
236
+ }
237
+ async listTools(timeoutMs) {
238
+ const out = [];
239
+ let cursor;
240
+ // Los servidores grandes pagina la lista; sin seguir el cursor se pierden
241
+ // herramientas en silencio.
242
+ for (let page = 0; page < 20; page++) {
243
+ const res = await this.request("tools/list", cursor ? { cursor } : {}, timeoutMs);
244
+ for (const t of res?.tools || []) {
245
+ out.push({ name: t.name, description: t.description, inputSchema: t.inputSchema });
246
+ }
247
+ cursor = res?.nextCursor;
248
+ if (!cursor)
249
+ break;
250
+ }
251
+ return out;
252
+ }
253
+ async callTool(toolName, args, timeoutMs) {
254
+ const limit = timeoutMs || this.config.timeout || 120_000;
255
+ const res = await this.request("tools/call", { name: toolName, arguments: args || {} }, limit);
256
+ // El resultado viene como una lista de bloques de contenido.
257
+ const parts = [];
258
+ for (const c of res?.content || []) {
259
+ if (c.type === "text")
260
+ parts.push(c.text);
261
+ else if (c.type === "image")
262
+ parts.push(`[imagen ${c.mimeType || "png"} devuelta por la herramienta]`);
263
+ else if (c.type === "resource")
264
+ parts.push(c.resource?.text || `[recurso ${c.resource?.uri || ""}]`);
265
+ else
266
+ parts.push(`[contenido ${c.type}]`);
267
+ }
268
+ const text = parts.join("\n").trim() || "(sin contenido)";
269
+ return res?.isError ? `El servidor devolvio un error: ${text}` : text;
270
+ }
271
+ disconnect() {
272
+ this.ready = false;
273
+ if (this.proc && !this.proc.killed) {
274
+ try {
275
+ this.proc.stdin.end();
276
+ this.proc.kill();
277
+ }
278
+ catch { }
279
+ }
280
+ this.proc = null;
281
+ }
282
+ }
@@ -0,0 +1,26 @@
1
+ import type { ChatCompletionTool } from "openai/resources/chat/completions";
2
+ import { McpServerConfig } from "./client.js";
3
+ export interface McpConfigFile {
4
+ mcpServers?: Record<string, McpServerConfig>;
5
+ }
6
+ export declare class McpManager {
7
+ private cwd;
8
+ private clients;
9
+ private toolIndex;
10
+ private loaded;
11
+ private configPaths;
12
+ constructor(cwd?: string);
13
+ readConfig(): Record<string, McpServerConfig>;
14
+ /** Conecta todos los servidores configurados. Devuelve un resumen legible. */
15
+ loadAll(timeoutMs?: number): Promise<string>;
16
+ summary(): string;
17
+ isMcpTool(name: string): boolean;
18
+ get toolCount(): number;
19
+ /** Definiciones en el formato de herramientas que consume el motor. */
20
+ getToolDefinitions(): ChatCompletionTool[];
21
+ callTool(fullName: string, args: Record<string, any>): Promise<string>;
22
+ /** Listado legible para el usuario, al estilo de /mcp. */
23
+ describe(): string;
24
+ shutdown(): void;
25
+ }
26
+ export declare const mcpManager: McpManager;
@@ -0,0 +1,167 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import os from "node:os";
4
+ import { McpClient } from "./client.js";
5
+ /**
6
+ * Gestor de servidores MCP.
7
+ *
8
+ * Lee la configuracion con la misma forma que usa Claude Code (.mcp.json con la
9
+ * clave "mcpServers"), conecta cada servidor y publica sus herramientas al
10
+ * agente con el nombre convencional mcp__<servidor>__<herramienta>.
11
+ *
12
+ * Precedencia, de mas a menos: proyecto -> usuario. Asi un equipo puede versionar
13
+ * .mcp.json en el repositorio y cada persona anadir los suyos aparte.
14
+ */
15
+ const MAX_TOOLS_PER_SERVER = 60;
16
+ /** Los nombres de herramienta solo admiten [A-Za-z0-9_-] en la API. */
17
+ function sanitize(name) {
18
+ return name.replace(/[^A-Za-z0-9_-]/g, "_");
19
+ }
20
+ export class McpManager {
21
+ cwd;
22
+ clients = new Map();
23
+ toolIndex = new Map();
24
+ loaded = false;
25
+ configPaths = [];
26
+ constructor(cwd = process.cwd()) {
27
+ this.cwd = cwd;
28
+ this.configPaths = [
29
+ // Proyecto: se versiona y se comparte con el equipo.
30
+ path.join(cwd, ".mcp.json"),
31
+ path.join(cwd, ".chocolatito", "mcp.json"),
32
+ // Usuario: vale para todos sus proyectos.
33
+ path.join(os.homedir(), ".chocolatito", "mcp.json"),
34
+ ];
35
+ }
36
+ readConfig() {
37
+ const merged = {};
38
+ // Se recorre de menor a mayor prioridad para que el proyecto gane.
39
+ for (const p of [...this.configPaths].reverse()) {
40
+ if (!fs.existsSync(p))
41
+ continue;
42
+ try {
43
+ const parsed = JSON.parse(fs.readFileSync(p, "utf-8"));
44
+ for (const [name, cfg] of Object.entries(parsed.mcpServers || {})) {
45
+ merged[name] = cfg;
46
+ }
47
+ }
48
+ catch {
49
+ // Un JSON roto no debe impedir arrancar el agente.
50
+ }
51
+ }
52
+ return merged;
53
+ }
54
+ /** Conecta todos los servidores configurados. Devuelve un resumen legible. */
55
+ async loadAll(timeoutMs = 20_000) {
56
+ if (this.loaded)
57
+ return this.summary();
58
+ this.loaded = true;
59
+ const config = this.readConfig();
60
+ const names = Object.keys(config);
61
+ if (names.length === 0)
62
+ return "";
63
+ // En paralelo: un servidor lento no debe retrasar a los demas.
64
+ await Promise.all(names.map(async (name) => {
65
+ const client = new McpClient(name, config[name]);
66
+ const ok = await client.connect(timeoutMs);
67
+ this.clients.set(name, client);
68
+ if (!ok)
69
+ return;
70
+ for (const tool of client.tools.slice(0, MAX_TOOLS_PER_SERVER)) {
71
+ const full = `mcp__${sanitize(name)}__${sanitize(tool.name)}`;
72
+ this.toolIndex.set(full, { client, toolName: tool.name });
73
+ }
74
+ }));
75
+ return this.summary();
76
+ }
77
+ summary() {
78
+ if (this.clients.size === 0)
79
+ return "";
80
+ const lines = [];
81
+ for (const [name, c] of this.clients) {
82
+ if (c.connected) {
83
+ lines.push(` ✔ ${name} (${c.transport}): ${c.tools.length} herramientas`);
84
+ }
85
+ else {
86
+ lines.push(` ✖ ${name}: ${c.lastError || "no conectado"}`);
87
+ }
88
+ }
89
+ return lines.join("\n");
90
+ }
91
+ isMcpTool(name) {
92
+ return this.toolIndex.has(name);
93
+ }
94
+ get toolCount() {
95
+ return this.toolIndex.size;
96
+ }
97
+ /** Definiciones en el formato de herramientas que consume el motor. */
98
+ getToolDefinitions() {
99
+ const out = [];
100
+ for (const [full, entry] of this.toolIndex) {
101
+ const meta = entry.client.tools.find((t) => t.name === entry.toolName);
102
+ const schema = meta?.inputSchema;
103
+ out.push({
104
+ type: "function",
105
+ function: {
106
+ name: full,
107
+ description: `[MCP · ${entry.client.name}] ${meta?.description || entry.toolName}`.slice(0, 1000),
108
+ // Algunos servidores no declaran esquema: hay que dar uno valido igual.
109
+ parameters: schema && typeof schema === "object" && schema.type
110
+ ? schema
111
+ : { type: "object", properties: {} },
112
+ },
113
+ });
114
+ }
115
+ return out;
116
+ }
117
+ async callTool(fullName, args) {
118
+ const entry = this.toolIndex.get(fullName);
119
+ if (!entry)
120
+ return `Error: la herramienta MCP "${fullName}" no esta disponible.`;
121
+ if (!entry.client.connected) {
122
+ return `Error: el servidor MCP "${entry.client.name}" no esta conectado (${entry.client.lastError || "?"}).`;
123
+ }
124
+ try {
125
+ return await entry.client.callTool(entry.toolName, args);
126
+ }
127
+ catch (err) {
128
+ return `Error llamando a ${fullName}: ${err?.message || String(err)}`;
129
+ }
130
+ }
131
+ /** Listado legible para el usuario, al estilo de /mcp. */
132
+ describe() {
133
+ const config = this.readConfig();
134
+ if (Object.keys(config).length === 0) {
135
+ return ("No hay servidores MCP configurados.\n\n" +
136
+ "Crea un archivo .mcp.json en la raiz del proyecto:\n" +
137
+ JSON.stringify({
138
+ mcpServers: {
139
+ "mi-servidor": {
140
+ command: "npx",
141
+ args: ["-y", "@modelcontextprotocol/server-filesystem", "."],
142
+ },
143
+ },
144
+ }, null, 2));
145
+ }
146
+ const lines = [`SERVIDORES MCP (${this.clients.size}):`, this.summary(), ""];
147
+ for (const [name, c] of this.clients) {
148
+ if (!c.connected || c.tools.length === 0)
149
+ continue;
150
+ lines.push(`${name}:`);
151
+ for (const t of c.tools.slice(0, MAX_TOOLS_PER_SERVER)) {
152
+ const desc = (t.description || "").split("\n")[0].slice(0, 90);
153
+ lines.push(` · mcp__${sanitize(name)}__${sanitize(t.name)} — ${desc}`);
154
+ }
155
+ lines.push("");
156
+ }
157
+ return lines.join("\n");
158
+ }
159
+ shutdown() {
160
+ for (const [, c] of this.clients)
161
+ c.disconnect();
162
+ this.clients.clear();
163
+ this.toolIndex.clear();
164
+ }
165
+ }
166
+ export const mcpManager = new McpManager(process.cwd());
167
+ process.on("exit", () => mcpManager.shutdown());
@@ -0,0 +1,9 @@
1
+ export declare class MemoryManager {
2
+ private memoryDir;
3
+ private memoryIndexPath;
4
+ constructor();
5
+ private ensureDirectory;
6
+ getMemoryIndex(): string;
7
+ saveMemory(topic: string, fact: string): string;
8
+ readMemory(topic: string): string;
9
+ }
@@ -0,0 +1,59 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import os from "node:os";
4
+ export class MemoryManager {
5
+ memoryDir;
6
+ memoryIndexPath;
7
+ constructor() {
8
+ this.memoryDir = path.join(os.homedir(), ".chocolatito", "memory");
9
+ this.memoryIndexPath = path.join(this.memoryDir, "MEMORY.md");
10
+ this.ensureDirectory();
11
+ }
12
+ ensureDirectory() {
13
+ if (!fs.existsSync(this.memoryDir)) {
14
+ fs.mkdirSync(this.memoryDir, { recursive: true });
15
+ }
16
+ if (!fs.existsSync(this.memoryIndexPath)) {
17
+ const defaultIndex = `# 🧠 Memoria Persistente de Chocolatito Code\n\nEste archivo indexa hechos, preferencias del usuario y aprendizajes clave entre proyectos.\n\n- [Preferencias Generales](./preferences.md): Preferencias de desarrollo y estilo.\n`;
18
+ fs.writeFileSync(this.memoryIndexPath, defaultIndex, "utf-8");
19
+ }
20
+ }
21
+ getMemoryIndex() {
22
+ this.ensureDirectory();
23
+ try {
24
+ return fs.readFileSync(this.memoryIndexPath, "utf-8");
25
+ }
26
+ catch {
27
+ return "Sin memoria previa.";
28
+ }
29
+ }
30
+ saveMemory(topic, fact) {
31
+ this.ensureDirectory();
32
+ const sanitized = topic.toLowerCase().replace(/[^a-z0-9_-]/g, "_");
33
+ const filePath = path.join(this.memoryDir, `${sanitized}.md`);
34
+ const timestamp = new Date().toISOString().split("T")[0];
35
+ const entry = `\n- [${timestamp}] ${fact}`;
36
+ if (fs.existsSync(filePath)) {
37
+ fs.appendFileSync(filePath, entry, "utf-8");
38
+ }
39
+ else {
40
+ fs.writeFileSync(filePath, `# Memoria: ${topic}\n${entry}\n`, "utf-8");
41
+ }
42
+ // Update MEMORY.md index if not already present
43
+ const indexContent = fs.readFileSync(this.memoryIndexPath, "utf-8");
44
+ if (!indexContent.includes(`${sanitized}.md`)) {
45
+ const link = `- [${topic}](./${sanitized}.md): Registro de ${topic}.\n`;
46
+ fs.appendFileSync(this.memoryIndexPath, link, "utf-8");
47
+ }
48
+ return `✔ Hecho guardado en memoria persistente (~/.chocolatito/memory/${sanitized}.md).`;
49
+ }
50
+ readMemory(topic) {
51
+ this.ensureDirectory();
52
+ const sanitized = topic.toLowerCase().replace(/[^a-z0-9_-]/g, "_");
53
+ const filePath = path.join(this.memoryDir, `${sanitized}.md`);
54
+ if (fs.existsSync(filePath)) {
55
+ return fs.readFileSync(filePath, "utf-8");
56
+ }
57
+ return `No hay memoria registrada para el tema "${topic}".`;
58
+ }
59
+ }
@@ -0,0 +1,5 @@
1
+ import { ProjectContext } from "../agent/context.js";
2
+ import { EspectroModel } from "../config/constants.js";
3
+ import { SkillManager } from "../skills/manager.js";
4
+ import { MemoryManager } from "../memory/manager.js";
5
+ export declare function buildSystemPrompt(context: ProjectContext, model: EspectroModel, skillManager?: SkillManager, memoryManager?: MemoryManager, isPlanMode?: boolean): string;