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,193 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import os from "node:os";
4
+ import { execa } from "execa";
5
+ const DEFAULT_TIMEOUT_MS = 30_000;
6
+ export class HookManager {
7
+ config = {};
8
+ disabled = false;
9
+ cwd;
10
+ constructor(cwd = process.cwd()) {
11
+ this.cwd = cwd;
12
+ this.load();
13
+ }
14
+ /** settings.json del usuario y del proyecto; el proyecto tiene prioridad. */
15
+ load() {
16
+ const paths = [
17
+ path.join(os.homedir(), ".chocolatito", "settings.json"),
18
+ path.join(this.cwd, ".chocolatito", "settings.json"),
19
+ ];
20
+ for (const p of paths) {
21
+ if (!fs.existsSync(p))
22
+ continue;
23
+ try {
24
+ const parsed = JSON.parse(fs.readFileSync(p, "utf-8"));
25
+ if (parsed.disableAllHooks === true)
26
+ this.disabled = true;
27
+ for (const [event, matchers] of Object.entries(parsed.hooks || {})) {
28
+ const key = event;
29
+ this.config[key] = [...(this.config[key] || []), ...matchers];
30
+ }
31
+ }
32
+ catch {
33
+ // Un settings.json roto no debe impedir arrancar.
34
+ }
35
+ }
36
+ }
37
+ get hasHooks() {
38
+ return !this.disabled && Object.keys(this.config).length > 0;
39
+ }
40
+ describe() {
41
+ if (this.disabled)
42
+ return "Los hooks estan desactivados (disableAllHooks).";
43
+ const events = Object.keys(this.config);
44
+ if (events.length === 0) {
45
+ return ("No hay hooks configurados.\n\n" +
46
+ "Se definen en .chocolatito/settings.json:\n" +
47
+ JSON.stringify({
48
+ hooks: {
49
+ PreToolUse: [
50
+ {
51
+ matcher: "run_command",
52
+ hooks: [{ type: "command", command: "node .chocolatito/hooks/guardia.mjs" }],
53
+ },
54
+ ],
55
+ },
56
+ }, null, 2));
57
+ }
58
+ const lines = ["HOOKS CONFIGURADOS:"];
59
+ for (const e of events) {
60
+ for (const m of this.config[e] || []) {
61
+ for (const h of m.hooks) {
62
+ lines.push(` ${e} [${m.matcher || "*"}] → ${h.command}`);
63
+ }
64
+ }
65
+ }
66
+ return lines.join("\n");
67
+ }
68
+ /**
69
+ * El matcher sigue las reglas de Claude Code: vacio o "*" vale para todo, un
70
+ * texto simple (o lista con "|") compara exacto, y cualquier otra cosa se
71
+ * trata como expresion regular.
72
+ */
73
+ matches(matcher, value) {
74
+ if (!matcher || matcher === "*" || matcher.trim() === "")
75
+ return true;
76
+ if (/^[A-Za-z0-9_\-, |]+$/.test(matcher)) {
77
+ return matcher
78
+ .split("|")
79
+ .map((s) => s.trim())
80
+ .filter(Boolean)
81
+ .includes(value);
82
+ }
83
+ try {
84
+ return new RegExp(matcher).test(value);
85
+ }
86
+ catch {
87
+ return false;
88
+ }
89
+ }
90
+ hooksFor(event, matchValue) {
91
+ if (this.disabled)
92
+ return [];
93
+ const out = [];
94
+ for (const m of this.config[event] || []) {
95
+ if (this.matches(m.matcher, matchValue))
96
+ out.push(...m.hooks);
97
+ }
98
+ return out;
99
+ }
100
+ /**
101
+ * Ejecuta los hooks del evento y devuelve la decision combinada.
102
+ * Gana el veto: si CUALQUIER hook deniega, se deniega.
103
+ */
104
+ async run(event, payload, matchValue = "") {
105
+ const hooks = this.hooksFor(event, matchValue);
106
+ if (hooks.length === 0)
107
+ return {};
108
+ const input = JSON.stringify({
109
+ hook_event_name: event,
110
+ cwd: this.cwd,
111
+ session_id: process.env.CHOCOLATITO_SESSION_ID || "",
112
+ ...payload,
113
+ });
114
+ const decision = {};
115
+ const contextos = [];
116
+ for (const hook of hooks) {
117
+ try {
118
+ const usaPowerShell = hook.shell === "powershell" || (hook.shell !== "bash" && process.platform === "win32");
119
+ // PowerShell NO propaga el codigo de salida del programa que ejecuta:
120
+ // powershell.exe sale con 0 aunque el hook haya salido con 2. Sin este
121
+ // "exit $LASTEXITCODE", el bloqueo por codigo de salida no funcionaba en
122
+ // Windows y el hook parecia estar aprobando siempre.
123
+ const comando = usaPowerShell ? `${hook.command}; exit $LASTEXITCODE` : hook.command;
124
+ const res = await execa(comando, {
125
+ cwd: this.cwd,
126
+ shell: usaPowerShell ? "powershell.exe" : "/bin/bash",
127
+ input,
128
+ timeout: hook.timeout ? hook.timeout * 1000 : DEFAULT_TIMEOUT_MS,
129
+ reject: false,
130
+ env: { ...process.env, CHOCOLATITO_PROJECT_DIR: this.cwd },
131
+ });
132
+ // Codigo 2 = bloqueo. No se puede sobrescribir con JSON: es el atajo
133
+ // para un hook de una linea que solo quiere decir "aqui no".
134
+ if (res.exitCode === 2) {
135
+ decision.permissionDecision = "deny";
136
+ decision.permissionDecisionReason =
137
+ (res.stderr || res.stdout || "").trim() || `El hook "${hook.command}" bloqueo la accion.`;
138
+ return decision;
139
+ }
140
+ const salida = (res.stdout || "").trim();
141
+ if (!salida)
142
+ continue;
143
+ if (salida.startsWith("{")) {
144
+ let parsed;
145
+ try {
146
+ parsed = JSON.parse(salida);
147
+ }
148
+ catch {
149
+ contextos.push(salida);
150
+ continue;
151
+ }
152
+ const esp = parsed.hookSpecificOutput || {};
153
+ if (parsed.decision === "block") {
154
+ decision.permissionDecision = "deny";
155
+ decision.permissionDecisionReason = parsed.reason || "Bloqueado por un hook.";
156
+ return decision;
157
+ }
158
+ if (esp.permissionDecision === "deny") {
159
+ decision.permissionDecision = "deny";
160
+ decision.permissionDecisionReason = esp.permissionDecisionReason || "Bloqueado por un hook.";
161
+ return decision;
162
+ }
163
+ // "allow" no gana sobre un "deny" posterior: por eso no se corta aqui.
164
+ if (esp.permissionDecision && !decision.permissionDecision) {
165
+ decision.permissionDecision = esp.permissionDecision;
166
+ decision.permissionDecisionReason = esp.permissionDecisionReason;
167
+ }
168
+ if (esp.updatedInput)
169
+ decision.updatedInput = { ...(decision.updatedInput || {}), ...esp.updatedInput };
170
+ if (esp.additionalContext)
171
+ contextos.push(esp.additionalContext);
172
+ if (parsed.systemMessage)
173
+ decision.systemMessage = parsed.systemMessage;
174
+ }
175
+ else {
176
+ // Salida en texto plano: se trata como contexto extra.
177
+ contextos.push(salida);
178
+ }
179
+ }
180
+ catch (err) {
181
+ contextos.push(`[hook "${hook.command}" fallo: ${err?.message || err}]`);
182
+ }
183
+ }
184
+ if (contextos.length > 0)
185
+ decision.additionalContext = contextos.join("\n");
186
+ return decision;
187
+ }
188
+ /** Compatibilidad con la API anterior. */
189
+ async triggerHook(event, context) {
190
+ const d = await this.run(event, context);
191
+ return d.additionalContext || null;
192
+ }
193
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,470 @@
1
+ #!/usr/bin/env node
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import chalk from "chalk";
6
+ import { ensureApiKey } from "./config/env.js";
7
+ import { ensureLicense, licenseStatus, deactivate, getStoredLicense } from "./config/license.js";
8
+ import { isManagedMode } from "./config/quota.js";
9
+ import { getEngineBaseUrl, DEFAULT_ESPECTRO, ESPECTRO_MODELS, RULES_FILE_NAME } from "./config/constants.js";
10
+ import { getProjectContext } from "./agent/context.js";
11
+ import { buildSystemPrompt } from "./prompts/systemPrompt.js";
12
+ import { AgentLoop } from "./agent/loop.js";
13
+ import { showBanner, animateStartupBanner, showHelp, showEffortMenu } from "./ui/banner.js";
14
+ import { askInteractivePrompt, SLASH_COMMANDS } from "./ui/prompt.js";
15
+ import { changeDir } from "./tools/changeDir.js";
16
+ import { gitDiff, gitCommit } from "./tools/gitTools.js";
17
+ import { renderUserPrompt } from "./ui/renderer.js";
18
+ import { parseFileMentions, buildPromptWithMentions } from "./agent/mentions.js";
19
+ import { SkillManager } from "./skills/manager.js";
20
+ import { MemoryManager } from "./memory/manager.js";
21
+ import { HookManager } from "./hooks/manager.js";
22
+ import { SessionManager } from "./sessions/manager.js";
23
+ import { globalUndoManager } from "./agent/undoManager.js";
24
+ import { setAutoApprove } from "./ui/permissionPrompt.js";
25
+ import { onModeChange, getMode, modeAnnouncement } from "./ui/modes.js";
26
+ import { releaseKeyboard } from "./ui/interrupt.js";
27
+ import { takeQueued, resetTypeAhead } from "./ui/typeAhead.js";
28
+ import { mcpManager } from "./mcp/manager.js";
29
+ /**
30
+ * Version leida del package.json instalado, no escrita a mano: una constante
31
+ * duplicada aqui se queda vieja en cuanto se publica una version nueva.
32
+ */
33
+ function readVersion() {
34
+ try {
35
+ const here = path.dirname(fileURLToPath(import.meta.url));
36
+ const pkg = JSON.parse(fs.readFileSync(path.resolve(here, "..", "package.json"), "utf-8"));
37
+ return String(pkg.version || "desconocida");
38
+ }
39
+ catch {
40
+ return "desconocida";
41
+ }
42
+ }
43
+ /**
44
+ * Banderas que se resuelven sin arrancar el agente.
45
+ *
46
+ * Antes no existian: "chocolatito --version" se tomaba como una peticion al
47
+ * modelo, pedia la API key, gastaba tokens y contestaba lo que le pareciera.
48
+ * Un usuario que acaba de instalar el CLI escribe eso o --help lo primero.
49
+ */
50
+ function handleFlags(argv) {
51
+ if (argv.includes("--version") || argv.includes("-v")) {
52
+ console.log(`chocolatito-code v${readVersion()}`);
53
+ return true;
54
+ }
55
+ if (argv.includes("--help") || argv.includes("-h")) {
56
+ const v = readVersion();
57
+ console.log([
58
+ `\nChocolatito Code v${v} — agente autonomo de programacion para la terminal.\n`,
59
+ "USO",
60
+ " chocolatito Sesion interactiva.",
61
+ ' chocolatito "haz X" Ejecuta una orden y termina.',
62
+ "",
63
+ "OPCIONES",
64
+ " -y, --yes Aprueba las acciones sin preguntar (util en CI).",
65
+ " -v, --version Muestra la version instalada.",
66
+ " -h, --help Muestra esta ayuda.",
67
+ "",
68
+ "DENTRO DE LA SESION",
69
+ " /effort 1.0|1.5|2.0|2.5 Cambia el nivel de potencia del motor.",
70
+ " /mcp Estado de los servidores MCP conectados.",
71
+ " /undo Deshace el ultimo cambio en archivos.",
72
+ " /resume [id] Reanuda una sesion guardada.",
73
+ " /exit Salir.",
74
+ "",
75
+ "CONFIGURACION",
76
+ " API key: variable CHOCOLATITO_API_KEY, un .env del proyecto o ~/.chocolatitorc.",
77
+ " Reglas del proyecto: CHOCOLATITO.md. Permisos y hooks: .chocolatito/settings.json.",
78
+ "",
79
+ ].join("\n"));
80
+ return true;
81
+ }
82
+ return false;
83
+ }
84
+ /**
85
+ * Devuelve la terminal como estaba.
86
+ *
87
+ * Mientras el agente trabaja, stdin esta en modo crudo: sin eco y sin edicion de
88
+ * linea. Si el proceso muere ahi (una excepcion, un kill), el usuario se queda
89
+ * con una terminal en la que escribe y no ve nada, y tiene que ejecutar 'reset'
90
+ * a ciegas. Restaurarlo cuesta tres lineas.
91
+ */
92
+ function restaurarTerminal() {
93
+ try {
94
+ releaseKeyboard();
95
+ }
96
+ catch { }
97
+ try {
98
+ if (process.stdin.isTTY && process.stdin.setRawMode)
99
+ process.stdin.setRawMode(false);
100
+ }
101
+ catch { }
102
+ try {
103
+ process.stdout.write("\x1b[?25h"); // cursor visible
104
+ }
105
+ catch { }
106
+ }
107
+ function instalarRedDeSeguridad() {
108
+ process.on("exit", restaurarTerminal);
109
+ // Una promesa sin capturar no deberia costarle al usuario la sesion entera,
110
+ // con todo su contexto. Se avisa y se sigue.
111
+ process.on("unhandledRejection", (motivo) => {
112
+ console.log(chalk.yellow(`\n⚠ Fallo en segundo plano: ${motivo?.message || String(motivo)}`) +
113
+ chalk.gray("\n La sesion continua. Si se repite, reinicia con /clear.\n"));
114
+ });
115
+ // Una excepcion no capturada si es terminal, pero al menos se sale con la
116
+ // terminal usable y con un mensaje legible en vez de una traza cruda.
117
+ process.on("uncaughtException", (err) => {
118
+ restaurarTerminal();
119
+ console.log(chalk.red(`\n✖ Error inesperado: ${err?.message || String(err)}\n`) +
120
+ chalk.gray(`${(err?.stack || "").split("\n").slice(1, 4).join("\n")}\n`));
121
+ process.exit(1);
122
+ });
123
+ }
124
+ async function main() {
125
+ if (handleFlags(process.argv.slice(2)))
126
+ return;
127
+ instalarRedDeSeguridad();
128
+ // La licencia va ANTES de la API key: no tiene sentido pedirle a alguien que
129
+ // configure DeepSeek si despues le vamos a decir que no ha comprado.
130
+ if (!(await ensureLicense())) {
131
+ process.exit(1);
132
+ }
133
+ // En suscripcion el motor lo paga Chocolatito: el cliente NO tiene ni necesita
134
+ // clave de DeepSeek, y su credencial es la propia licencia. Preguntarsela era
135
+ // dejarle atascado en una pantalla que no puede contestar.
136
+ const apiKey = isManagedMode()
137
+ ? getStoredLicense()?.key || (await ensureApiKey())
138
+ : await ensureApiKey();
139
+ const context = await getProjectContext(process.cwd());
140
+ const skillManager = new SkillManager(context.cwd);
141
+ const memoryManager = new MemoryManager();
142
+ const hookManager = new HookManager(context.cwd);
143
+ const sessionManager = new SessionManager();
144
+ const sessionId = `session_${Date.now()}`;
145
+ let currentModel = DEFAULT_ESPECTRO;
146
+ let isPlanMode = false;
147
+ let systemPrompt = buildSystemPrompt(context, currentModel, skillManager, memoryManager, isPlanMode);
148
+ const agent = new AgentLoop(apiKey, getEngineBaseUrl(), currentModel, systemPrompt);
149
+ // Shift+Tab (en el prompt, durante la generacion o dentro del propio dialogo)
150
+ // cambia el modo de permisos del agente al vuelo. El modo plan no entra en el
151
+ // ciclo: se sigue activando con /plan, para no dejarlo a un golpe de tecla.
152
+ onModeChange((modo) => {
153
+ if (!isPlanMode)
154
+ agent.permissions.setMode(modo);
155
+ });
156
+ // Trigger SessionStart hook if configured
157
+ await hookManager.triggerHook("SessionStart", { cwd: context.cwd, model: currentModel.name });
158
+ // Servidores MCP: se conectan en segundo plano para no retrasar el arranque.
159
+ // Si tardan, el primer turno los espera; el usuario ya puede escribir.
160
+ const mcpReady = mcpManager.loadAll().then((resumen) => {
161
+ if (resumen) {
162
+ console.log(chalk.hex("#D97757")("\n🔌 Servidores MCP:"));
163
+ console.log(resumen);
164
+ console.log(chalk.gray(` ${mcpManager.toolCount} herramientas MCP disponibles.\n`));
165
+ }
166
+ return resumen;
167
+ });
168
+ // Add /resume to slash commands
169
+ if (!SLASH_COMMANDS.some((c) => c.name === "/licencia")) {
170
+ SLASH_COMMANDS.push({ name: "/licencia", description: "Ver la licencia activa o liberar este equipo", args: "[liberar]" });
171
+ }
172
+ if (!SLASH_COMMANDS.some((c) => c.name === "/resume")) {
173
+ SLASH_COMMANDS.splice(1, 0, { name: "/resume", description: "Reanudar una sesión anterior guardada", args: "[id]" });
174
+ }
175
+ // Single-command mode: chocolatito "crea index.html"
176
+ const rawArgs = process.argv.slice(2);
177
+ // --yes / -y aprueba las acciones sin preguntar. Util en modo comando unico y CI.
178
+ const autoYes = rawArgs.includes("--yes") || rawArgs.includes("-y");
179
+ if (autoYes)
180
+ setAutoApprove(true);
181
+ const args = rawArgs.filter((a) => a !== "--yes" && a !== "-y");
182
+ if (args.length > 0) {
183
+ const singlePrompt = args.join(" ");
184
+ showBanner(context.cwd, currentModel, "idle");
185
+ renderUserPrompt(singlePrompt);
186
+ const { attachedFiles } = parseFileMentions(singlePrompt, context.cwd);
187
+ const enrichedPrompt = buildPromptWithMentions(singlePrompt, attachedFiles);
188
+ await mcpReady;
189
+ await agent.runTask(enrichedPrompt);
190
+ process.exit(0);
191
+ }
192
+ // Interactive REPL Mode with lively startup walking animation
193
+ await animateStartupBanner(context.cwd, currentModel);
194
+ // El atajo hay que anunciarlo: un modo que nadie sabe que existe no sirve.
195
+ console.log(chalk.gray(" shift+tab cambia el modo de permisos · puedes escribir mientras trabaja y se encola\n"));
196
+ if (getMode() !== "default")
197
+ console.log(` ${modeAnnouncement()}\n`);
198
+ // Lo que el usuario escribio mientras el agente trabajaba entra como siguiente
199
+ // orden, sin tener que volver a teclearlo.
200
+ let pendiente = null;
201
+ while (true) {
202
+ let userInput = "";
203
+ if (pendiente) {
204
+ userInput = pendiente;
205
+ pendiente = null;
206
+ renderUserPrompt(userInput);
207
+ }
208
+ else {
209
+ try {
210
+ userInput = await askInteractivePrompt(isPlanMode ? chalk.cyan("plan ❯") : "❯", agent.currentCwd);
211
+ }
212
+ catch {
213
+ console.log(chalk.gray("\n👋 Sesión finalizada."));
214
+ await hookManager.triggerHook("SessionEnd", { sessionId, cwd: agent.currentCwd });
215
+ process.exit(0);
216
+ }
217
+ }
218
+ const trimmed = userInput.trim();
219
+ if (!trimmed)
220
+ continue;
221
+ // Handle Slash Commands
222
+ if (trimmed === "/exit" || trimmed === "exit" || trimmed === "quit") {
223
+ console.log(chalk.gray("\n👋 Sesión finalizada."));
224
+ await hookManager.triggerHook("SessionEnd", { sessionId, cwd: agent.currentCwd });
225
+ process.exit(0);
226
+ }
227
+ if (trimmed === "/licencia" || trimmed.startsWith("/licencia ")) {
228
+ const sub = trimmed.slice("/licencia".length).trim();
229
+ if (sub === "liberar") {
230
+ const res = await deactivate();
231
+ console.log(res.ok ? chalk.green(`\n${res.message}\n`) : chalk.yellow(`\n${res.message}\n`));
232
+ }
233
+ else {
234
+ console.log(`\n${licenseStatus()}\n`);
235
+ }
236
+ continue;
237
+ }
238
+ if (trimmed === "/undo") {
239
+ const undoRes = globalUndoManager.undoLast();
240
+ if (undoRes.success) {
241
+ console.log(chalk.green(`\n${undoRes.message}`));
242
+ undoRes.restoredFiles.forEach((f) => console.log(` ${chalk.gray("•")} ${chalk.white(f)}`));
243
+ console.log("");
244
+ }
245
+ else {
246
+ console.log(chalk.yellow(`\n⚠ ${undoRes.message}\n`));
247
+ }
248
+ continue;
249
+ }
250
+ if (trimmed.startsWith("/export")) {
251
+ const targetName = trimmed.slice(7).trim() || `chocolatito_session_${Date.now()}.md`;
252
+ const exportPath = path.isAbsolute(targetName) ? targetName : path.resolve(agent.currentCwd, targetName);
253
+ const sessionData = agent.messages
254
+ .filter((m) => m.role === "user" || m.role === "assistant")
255
+ .map((m) => `### 👤 ${m.role === "user" ? "Usuario" : "Chocolatito"}\n\n${typeof m.content === "string" ? m.content : JSON.stringify(m.content, null, 2)}\n`)
256
+ .join("\n---\n\n");
257
+ const mdContent = `# 🦊 Bitácora de Sesión — Chocolatito Code
258
+ **Fecha:** ${new Date().toLocaleString()}
259
+ **Directorio:** \`${agent.currentCwd}\`
260
+ **Motor:** ${currentModel.name}
261
+ **Inversión Total:** ${agent.tracker.calculateCost(undefined, undefined, undefined, currentModel.underlyingModel).formattedCost}
262
+
263
+ ---
264
+
265
+ ${sessionData}
266
+ `;
267
+ fs.writeFileSync(exportPath, mdContent, "utf-8");
268
+ console.log(chalk.green(`\n✔ Bitácora exportada exitosamente en: ${chalk.bold.white(exportPath)}\n`));
269
+ continue;
270
+ }
271
+ if (trimmed === "/help") {
272
+ showHelp();
273
+ continue;
274
+ }
275
+ if (trimmed.startsWith("/resume")) {
276
+ const targetId = trimmed.slice(7).trim();
277
+ const loaded = targetId ? sessionManager.loadSession(targetId) : sessionManager.loadLatestSession();
278
+ if (loaded) {
279
+ agent.messages = loaded.messages;
280
+ console.log(chalk.green(`\n✔ Sesión "${loaded.id}" restaurada con éxito (${loaded.messages.length} mensajes cargados).\n`));
281
+ }
282
+ else {
283
+ const available = sessionManager.listSessions();
284
+ console.log(chalk.yellow("\n⚠ No se encontró la sesión solicitada. Sesiones disponibles:"));
285
+ available.forEach((s) => console.log(` • ${chalk.white(s.id)} (${s.updatedAt}) en ${chalk.gray(s.cwd)}`));
286
+ console.log("");
287
+ }
288
+ continue;
289
+ }
290
+ if (trimmed === "/plan") {
291
+ isPlanMode = !isPlanMode;
292
+ agent.permissions.setMode(isPlanMode ? "plan" : "default");
293
+ const refreshedContext = await getProjectContext(agent.currentCwd);
294
+ systemPrompt = buildSystemPrompt(refreshedContext, currentModel, skillManager, memoryManager, isPlanMode);
295
+ agent.setEspectroModel(currentModel, systemPrompt);
296
+ if (isPlanMode) {
297
+ console.log(chalk.cyan.bold("\n📋 MODO PLAN ACTIVADO (Solo lectura)") +
298
+ chalk.gray("\n El agente investigará y propondrá planes de arquitectura sin modificar ningún archivo.\n Escribe /plan nuevamente para desactivarlo.\n"));
299
+ }
300
+ else {
301
+ console.log(chalk.green.bold("\n🚀 MODO PLAN DESACTIVADO (Modo ejecución normal)") +
302
+ chalk.gray("\n Permisos de edición y ejecución habilitados.\n"));
303
+ }
304
+ continue;
305
+ }
306
+ if (trimmed === "/mcp") {
307
+ await mcpReady;
308
+ console.log(chalk.bold.hex("#D97757")("\n🔌 MODEL CONTEXT PROTOCOL"));
309
+ console.log(mcpManager.describe());
310
+ console.log("");
311
+ continue;
312
+ }
313
+ if (trimmed === "/skills") {
314
+ const skills = skillManager.scanSkills();
315
+ console.log(chalk.bold.hex("#D97757")("\n🧩 SKILLS DISPONIBLES (Carga diferida):"));
316
+ if (skills.length === 0) {
317
+ console.log(chalk.gray(" No hay skills en ~/.chocolatito/skills ni ~/.claude/skills.\n"));
318
+ }
319
+ else {
320
+ skills.forEach((s) => {
321
+ console.log(` ${chalk.hex("#A855F7")("•")} ${chalk.bold.white(s.name)}\n ${chalk.gray(s.description)}`);
322
+ });
323
+ console.log(chalk.italic.gray("\n (Chocolatito cargará el contenido completo automáticamente cuando sea necesario).\n"));
324
+ }
325
+ continue;
326
+ }
327
+ if (trimmed === "/memory") {
328
+ console.log(chalk.bold.hex("#A855F7")("\n🧠 ÍNDICE DE MEMORIA PERSISTENTE (~/.chocolatito/memory/):"));
329
+ console.log(chalk.gray(memoryManager.getMemoryIndex()) + "\n");
330
+ continue;
331
+ }
332
+ if (trimmed.startsWith("/goal")) {
333
+ const goalText = trimmed.slice(5).trim();
334
+ if (!goalText) {
335
+ console.log(chalk.yellow("\n⚠ Uso: /goal <descripción de la meta compleja>\n"));
336
+ continue;
337
+ }
338
+ console.log(chalk.bold.hex("#A855F7")("\n🎯 INICIANDO MODO META AUTÓNOMA (GOAL MODE):"));
339
+ console.log(chalk.gray(` Objetivo: "${goalText}"\n`));
340
+ const autonomousPrompt = `[MODO META AUTÓNOMA ACTIVADO]
341
+ Tu objetivo principal es: "${goalText}"
342
+ Directivas de ejecución:
343
+ 1. Formula un plan de ejecución mental paso a paso.
344
+ 2. Ejecuta cada paso utilizando las herramientas correspondientes (creación, edición, ejecución, verificación).
345
+ 3. Si algo falla o hay errores, autocorregir inmediatamente y volver a probar.
346
+ 4. No te detengas hasta verificar que el objetivo esté 100% completado con éxito.`;
347
+ renderUserPrompt(`/goal ${goalText}`);
348
+ await agent.runTask(autonomousPrompt);
349
+ sessionManager.saveSession({
350
+ id: sessionId,
351
+ cwd: agent.currentCwd,
352
+ modelId: currentModel.id,
353
+ createdAt: new Date().toISOString(),
354
+ updatedAt: new Date().toISOString(),
355
+ messages: agent.messages,
356
+ });
357
+ continue;
358
+ }
359
+ if (trimmed === "/compact") {
360
+ const refreshedContext = await getProjectContext(agent.currentCwd);
361
+ const res = await agent.compactHistory(buildSystemPrompt(refreshedContext, currentModel, skillManager, memoryManager, isPlanMode));
362
+ console.log(chalk.green(`\n${res}\n`));
363
+ continue;
364
+ }
365
+ if (trimmed === "/diff") {
366
+ const diffOut = await gitDiff(false, agent.currentCwd);
367
+ console.log(chalk.bold.hex("#D97757")("\n📝 DIFERENCIAS DE GIT (DIFF):"));
368
+ console.log(diffOut ? chalk.gray(diffOut) : chalk.green("✔ Sin cambios pendientes.") + "\n");
369
+ continue;
370
+ }
371
+ if (trimmed.startsWith("/commit")) {
372
+ const msg = trimmed.slice(7).trim();
373
+ if (!msg) {
374
+ console.log(chalk.yellow("\n⚠ Uso: /commit <mensaje del commit>\n"));
375
+ }
376
+ else {
377
+ const commitRes = await gitCommit(msg, true, agent.currentCwd);
378
+ console.log(chalk.green(`\n${commitRes}\n`));
379
+ }
380
+ continue;
381
+ }
382
+ if (trimmed.startsWith("/cd")) {
383
+ const target = trimmed.slice(3).trim() || "~";
384
+ const res = await changeDir(target, agent.currentCwd);
385
+ if (res.success) {
386
+ agent.currentCwd = res.newCwd;
387
+ const refreshedContext = await getProjectContext(agent.currentCwd);
388
+ systemPrompt = buildSystemPrompt(refreshedContext, currentModel, skillManager, memoryManager, isPlanMode);
389
+ agent.clearHistory(systemPrompt);
390
+ console.clear();
391
+ showBanner(agent.currentCwd, currentModel, "happy");
392
+ console.log(chalk.green(`✔ ${res.message}\n`));
393
+ }
394
+ else {
395
+ console.log(chalk.red(`\n${res.message}\n`));
396
+ }
397
+ continue;
398
+ }
399
+ if (trimmed === "/cost") {
400
+ const { formattedCost, inputCost, outputCost } = agent.tracker.calculateCost(undefined, undefined, undefined, currentModel.underlyingModel);
401
+ console.log(chalk.bold.hex("#A855F7")("\n📊 REPORTE DE CONSUMO DE SESIÓN:") +
402
+ `\n ${chalk.gray("• Motor activo:")} ${chalk.bold.white(currentModel.name)}` +
403
+ `\n ${chalk.gray("• Tokens entrada:")} ${agent.tracker.totalPromptTokens.toLocaleString()} ($${inputCost.toFixed(5)} USD)` +
404
+ `\n ${chalk.gray("• Tokens memoria:")} ${agent.tracker.cachedPromptTokens.toLocaleString()} (90% de ahorro en caché)` +
405
+ `\n ${chalk.gray("• Tokens salida:")} ${agent.tracker.totalCompletionTokens.toLocaleString()} ($${outputCost.toFixed(5)} USD)` +
406
+ `\n ${chalk.gray("• Inversión total:")} ${chalk.bold.green(formattedCost)}\n`);
407
+ continue;
408
+ }
409
+ if (trimmed.startsWith("/effort") || trimmed.startsWith("/espectro") || trimmed === "/model") {
410
+ const parts = trimmed.split(/\s+/);
411
+ const targetId = parts[1]?.trim();
412
+ if (!targetId || !ESPECTRO_MODELS[targetId]) {
413
+ showEffortMenu(currentModel.id);
414
+ }
415
+ else {
416
+ currentModel = ESPECTRO_MODELS[targetId];
417
+ const refreshedContext = await getProjectContext(agent.currentCwd);
418
+ const updatedPrompt = buildSystemPrompt(refreshedContext, currentModel, skillManager, memoryManager, isPlanMode);
419
+ agent.setEspectroModel(currentModel, updatedPrompt);
420
+ console.log(chalk.green(`\n✔ Nivel actualizado a `) +
421
+ chalk.bold.hex("#A855F7")(currentModel.name) +
422
+ chalk.gray(` (${currentModel.effortLevel})`) +
423
+ `\n ${chalk.italic.gray(currentModel.description)}\n`);
424
+ }
425
+ continue;
426
+ }
427
+ if (trimmed === "/clear") {
428
+ const refreshedContext = await getProjectContext(agent.currentCwd);
429
+ systemPrompt = buildSystemPrompt(refreshedContext, currentModel, skillManager, memoryManager, isPlanMode);
430
+ agent.clearHistory(systemPrompt);
431
+ resetTypeAhead();
432
+ console.clear();
433
+ showBanner(agent.currentCwd, currentModel, "happy");
434
+ console.log(chalk.green("✔ Historial reiniciado.\n"));
435
+ continue;
436
+ }
437
+ if (trimmed === "/init") {
438
+ const rulesPath = path.join(agent.currentCwd, RULES_FILE_NAME);
439
+ if (!fs.existsSync(rulesPath)) {
440
+ const template = `# 🦊 Reglas del Proyecto (Chocolatito Code)\n\n## 📌 Directivas\n- Lenguaje principal:\n- Framework:\n- Convenciones de código:\n- Pruebas y validación:\n`;
441
+ fs.writeFileSync(rulesPath, template, "utf-8");
442
+ console.log(chalk.green(`\n✔ Creado ${RULES_FILE_NAME} en la raíz del proyecto.\n`));
443
+ }
444
+ else {
445
+ console.log(chalk.yellow(`\n⚠ El archivo ${RULES_FILE_NAME} ya existe.\n`));
446
+ }
447
+ continue;
448
+ }
449
+ // Parse and auto-attach @file mentions
450
+ const { attachedFiles } = parseFileMentions(trimmed, agent.currentCwd);
451
+ const enrichedPrompt = buildPromptWithMentions(trimmed, attachedFiles);
452
+ // Execute task with agent loop
453
+ await agent.runTask(enrichedPrompt);
454
+ // Lo que se escribio mientras trabajaba pasa a ser la siguiente orden.
455
+ pendiente = takeQueued();
456
+ // Persist session state
457
+ sessionManager.saveSession({
458
+ id: sessionId,
459
+ cwd: agent.currentCwd,
460
+ modelId: currentModel.id,
461
+ createdAt: new Date().toISOString(),
462
+ updatedAt: new Date().toISOString(),
463
+ messages: agent.messages,
464
+ });
465
+ }
466
+ }
467
+ main().catch((err) => {
468
+ console.error(chalk.red(`\nError: ${err?.message || String(err)}`));
469
+ process.exit(1);
470
+ });