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,466 @@
1
+ import readline from "node:readline";
2
+ import process from "node:process";
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+ import os from "node:os";
6
+ import chalk from "chalk";
7
+ import { cycleMode, describeMode, getMode } from "./modes.js";
8
+ import { releaseKeyboard } from "./interrupt.js";
9
+ import { clearTyped } from "./typeAhead.js";
10
+ export const SLASH_COMMANDS = [
11
+ { name: "/mcp", description: "Ver servidores MCP conectados y sus herramientas" },
12
+ { name: "/undo", description: "Deshacer las últimas modificaciones de archivos de la sesión" },
13
+ { name: "/export", description: "Exportar bitácora de la sesión a archivo Markdown", args: "[nombre.md]" },
14
+ { name: "/plan", description: "Alternar Modo Plan (solo lectura e investigación previa)" },
15
+ { name: "/resume", description: "Reanudar una sesión anterior guardada", args: "[id]" },
16
+ { name: "/skills", description: "Ver skills disponibles y cómo usarlos" },
17
+ { name: "/memory", description: "Consultar la memoria persistente transversal (~/.chocolatito/memory/)" },
18
+ { name: "/effort", description: "Regular potencia del motor (1.0, 1.5, 2.0, 2.5)", args: "[nivel]" },
19
+ { name: "/goal", description: "Ejecutar meta compleja autónoma con auto-corrección", args: "<meta>" },
20
+ { name: "/compact", description: "Comprimir contexto y optimizar memoria de la sesión" },
21
+ { name: "/diff", description: "Ver diferencias de código (Git diff) del proyecto" },
22
+ { name: "/commit", description: "Crear un commit en Git con mensaje semántico", args: "<mensaje>" },
23
+ { name: "/cd", description: "Cambiar el directorio de trabajo activo", args: "<ruta>" },
24
+ { name: "/cost", description: "Ver consumo detallado de tokens e inversión en USD" },
25
+ { name: "/clear", description: "Limpiar historial y reiniciar contexto" },
26
+ { name: "/init", description: "Crear archivo de directivas CHOCOLATITO.md" },
27
+ { name: "/help", description: "Mostrar guía completa de comandos y ayuda" },
28
+ { name: "/exit", description: "Cerrar sesión de Chocolatito Code" },
29
+ ];
30
+ let isKeypressInitialized = false;
31
+ function stripAnsi(str) {
32
+ return str.replace(/\x1b\[[0-9;]*m/g, "");
33
+ }
34
+ function getHistoryFilePath() {
35
+ const dir = path.join(os.homedir(), ".chocolatito");
36
+ if (!fs.existsSync(dir))
37
+ fs.mkdirSync(dir, { recursive: true });
38
+ return path.join(dir, "history");
39
+ }
40
+ function loadHistory() {
41
+ const file = getHistoryFilePath();
42
+ if (fs.existsSync(file)) {
43
+ try {
44
+ return fs.readFileSync(file, "utf-8").split("\n").filter((l) => l.trim().length > 0);
45
+ }
46
+ catch { }
47
+ }
48
+ return [];
49
+ }
50
+ function appendHistory(prompt) {
51
+ if (!prompt.trim())
52
+ return;
53
+ const file = getHistoryFilePath();
54
+ try {
55
+ fs.appendFileSync(file, `${prompt.trim()}\n`, "utf-8");
56
+ }
57
+ catch { }
58
+ }
59
+ function scanProjectFiles(dir, maxResults = 40) {
60
+ const results = [];
61
+ const ignore = new Set(["node_modules", ".git", "dist", "build", ".next", "out", "venv", "__pycache__", ".turbo"]);
62
+ function walk(current, depth = 0) {
63
+ if (depth > 6 || results.length >= maxResults)
64
+ return;
65
+ try {
66
+ const entries = fs.readdirSync(current, { withFileTypes: true });
67
+ for (const ent of entries) {
68
+ if (ent.name.startsWith(".") && ent.name !== ".env")
69
+ continue;
70
+ if (ignore.has(ent.name))
71
+ continue;
72
+ const full = path.join(current, ent.name);
73
+ const rel = path.relative(dir, full).replace(/\\/g, "/");
74
+ if (ent.isDirectory()) {
75
+ walk(full, depth + 1);
76
+ }
77
+ else if (ent.isFile()) {
78
+ results.push(rel);
79
+ if (results.length >= maxResults)
80
+ break;
81
+ }
82
+ }
83
+ }
84
+ catch { }
85
+ }
86
+ walk(dir);
87
+ return results;
88
+ }
89
+ export async function askInteractivePrompt(promptSymbol = "❯", cwd = process.cwd()) {
90
+ return new Promise((resolve) => {
91
+ let inputBuffer = "";
92
+ let cursorIndex = 0;
93
+ let selectedIndex = 0;
94
+ let isFinished = false;
95
+ let lastRenderedLines = [];
96
+ let lastCursorLineIndex = 1;
97
+ const history = loadHistory();
98
+ let historyIdx = history.length;
99
+ // Cache project files for @ autocompletion
100
+ let projectFiles = [];
101
+ try {
102
+ projectFiles = scanProjectFiles(cwd);
103
+ }
104
+ catch { }
105
+ if (!isKeypressInitialized) {
106
+ readline.emitKeypressEvents(process.stdin);
107
+ isKeypressInitialized = true;
108
+ }
109
+ // El prompt reclama el teclado antes de dibujarse. Si un vigilante de la
110
+ // generacion anterior se quedo escuchando (el bucle tiene muchas salidas y
111
+ // basta que una se escape), el usuario teclearia y no aparecerian sus letras
112
+ // aqui: se las estaria comiendo el vigilante. Reclamarlo cuesta nada y
113
+ // convierte veinte sitios que hay que acertar en uno.
114
+ releaseKeyboard();
115
+ // Lo que quedara a medio escribir del turno anterior no es de este prompt.
116
+ clearTyped();
117
+ const wasRaw = process.stdin.isRaw;
118
+ if (process.stdin.setRawMode) {
119
+ process.stdin.setRawMode(true);
120
+ }
121
+ process.stdin.resume();
122
+ function getAtMention() {
123
+ const textBeforeCursor = inputBuffer.slice(0, cursorIndex);
124
+ const match = textBeforeCursor.match(/@([^\s]*)$/);
125
+ if (match && match.index !== undefined) {
126
+ return {
127
+ active: true,
128
+ query: match[1].toLowerCase(),
129
+ startIndex: match.index,
130
+ };
131
+ }
132
+ return { active: false, query: "", startIndex: -1 };
133
+ }
134
+ function getDropdownItems() {
135
+ // 1. Slash commands
136
+ if (inputBuffer.startsWith("/")) {
137
+ const query = inputBuffer.toLowerCase();
138
+ return SLASH_COMMANDS
139
+ .filter((cmd) => cmd.name.toLowerCase().startsWith(query) || query === "/")
140
+ .map((cmd) => ({
141
+ type: "command",
142
+ label: `${cmd.name}${cmd.args ? " " + cmd.args : ""}`,
143
+ secondary: cmd.description,
144
+ insertText: cmd.name,
145
+ }));
146
+ }
147
+ // 2. @ File mentions
148
+ const at = getAtMention();
149
+ if (at.active) {
150
+ return projectFiles
151
+ .filter((f) => f.toLowerCase().includes(at.query))
152
+ .slice(0, 8)
153
+ .map((f) => ({
154
+ type: "file",
155
+ label: `@${f}`,
156
+ secondary: "Archivo del proyecto",
157
+ insertText: `@${f} `,
158
+ }));
159
+ }
160
+ return [];
161
+ }
162
+ function render() {
163
+ if (isFinished)
164
+ return;
165
+ const cols = Math.max(20, process.stdout.columns || 80);
166
+ // 1. Top separator line
167
+ const topBar = chalk.gray("─".repeat(cols));
168
+ // 2. Prompt symbol & input text
169
+ const symbolStr = promptSymbol.includes("plan")
170
+ ? `${chalk.cyan.bold("plan ❯")} `
171
+ : `${chalk.bold.hex("#D97757")("❯")} `;
172
+ const rawSymbolLen = promptSymbol.includes("plan") ? 7 : 2;
173
+ const placeholderText = "Escribe una instrucción, '@' para archivos o '/' para comandos...";
174
+ let formattedInput = "";
175
+ if (inputBuffer.length === 0) {
176
+ formattedInput = chalk.gray(placeholderText);
177
+ }
178
+ else if (inputBuffer.startsWith("/")) {
179
+ const parts = inputBuffer.split(" ");
180
+ const cmdPart = chalk.bold.hex("#A855F7")(parts[0]);
181
+ const restPart = parts.slice(1).join(" ");
182
+ formattedInput = restPart ? `${cmdPart} ${chalk.white(restPart)}` : cmdPart;
183
+ }
184
+ else {
185
+ formattedInput = inputBuffer.replace(/(@[^\s]+)/g, (m) => chalk.bold.hex("#D97757")(m));
186
+ }
187
+ // 3. Dropdown items (if any)
188
+ const items = getDropdownItems();
189
+ const dropdownLines = [];
190
+ if (items.length > 0) {
191
+ if (selectedIndex >= items.length)
192
+ selectedIndex = 0;
193
+ if (selectedIndex < 0)
194
+ selectedIndex = items.length - 1;
195
+ items.forEach((item, idx) => {
196
+ const isSelected = idx === selectedIndex;
197
+ const isFile = item.type === "file";
198
+ const colorHex = isFile ? "#D97757" : "#A855F7";
199
+ const pointer = isSelected
200
+ ? chalk.bold.hex(colorHex)(" ❯ ")
201
+ : chalk.gray(" ");
202
+ const label = isSelected
203
+ ? chalk.bold.white(item.label)
204
+ : chalk.hex(colorHex)(item.label);
205
+ const desc = isSelected
206
+ ? chalk.whiteBright(` ${item.secondary || ""}`)
207
+ : chalk.gray(` ${item.secondary || ""}`);
208
+ dropdownLines.push(`${pointer}${label}${desc}`);
209
+ });
210
+ }
211
+ // 4. Bottom separator line
212
+ const bottomBar = chalk.gray("─".repeat(cols));
213
+ // 5. Footer status line
214
+ const modeLook = describeMode(getMode());
215
+ const leftFooter = chalk.gray("? /help para atajos · shift+tab modo · esc cancelar");
216
+ const rightFooter = `${chalk.hex(modeLook.color)(`⇥ modo ${modeLook.label}`)} ${chalk.gray("· Espectro 1.0 (Ágil)")}`;
217
+ const leftLen = stripAnsi(leftFooter).length;
218
+ const rightLen = stripAnsi(rightFooter).length;
219
+ const padLen = Math.max(1, cols - leftLen - rightLen);
220
+ const footerLine = `${leftFooter}${" ".repeat(padLen)}${rightFooter}`;
221
+ // Build current frame array
222
+ const currentFrame = [topBar, symbolStr + formattedInput];
223
+ if (dropdownLines.length > 0) {
224
+ currentFrame.push(...dropdownLines);
225
+ }
226
+ currentFrame.push(bottomBar, footerLine);
227
+ const inputLineIdx = 1;
228
+ const totalCharsCursor = rawSymbolLen + (inputBuffer.length === 0 ? 0 : cursorIndex);
229
+ const cursorCol = totalCharsCursor % cols;
230
+ if (lastRenderedLines.length === 0) {
231
+ // First render: write frame and position cursor on input line
232
+ process.stdout.write(currentFrame.join("\n"));
233
+ const moveUp = currentFrame.length - 1 - inputLineIdx;
234
+ process.stdout.write(`\x1b[${moveUp}A\r\x1b[${cursorCol}C`);
235
+ }
236
+ else {
237
+ // Subsequent render: re-draw lines in place with 0 scrolling drift
238
+ if (lastCursorLineIndex > 0) {
239
+ process.stdout.write(`\x1b[${lastCursorLineIndex}A\r`);
240
+ }
241
+ else {
242
+ process.stdout.write("\r");
243
+ }
244
+ const maxLines = Math.max(currentFrame.length, lastRenderedLines.length);
245
+ for (let i = 0; i < maxLines; i++) {
246
+ const content = i < currentFrame.length ? currentFrame[i] : "";
247
+ process.stdout.write(`\x1b[2K${content}`);
248
+ if (i < maxLines - 1) {
249
+ process.stdout.write("\x1b[1E");
250
+ }
251
+ }
252
+ const moveUp = (maxLines - 1) - inputLineIdx;
253
+ if (moveUp > 0) {
254
+ process.stdout.write(`\x1b[${moveUp}A\r\x1b[${cursorCol}C`);
255
+ }
256
+ else {
257
+ process.stdout.write(`\r\x1b[${cursorCol}C`);
258
+ }
259
+ }
260
+ lastRenderedLines = currentFrame;
261
+ lastCursorLineIndex = inputLineIdx;
262
+ }
263
+ function cleanup() {
264
+ isFinished = true;
265
+ process.stdin.removeListener("keypress", onKeypress);
266
+ if (process.stdin.setRawMode) {
267
+ process.stdin.setRawMode(wasRaw || false);
268
+ }
269
+ }
270
+ function applySelectedDropdownItem() {
271
+ const items = getDropdownItems();
272
+ if (items.length === 0)
273
+ return false;
274
+ const item = items[selectedIndex];
275
+ if (item.type === "command") {
276
+ inputBuffer = item.insertText;
277
+ cursorIndex = inputBuffer.length;
278
+ return true;
279
+ }
280
+ else if (item.type === "file") {
281
+ const at = getAtMention();
282
+ if (at.active) {
283
+ inputBuffer = inputBuffer.slice(0, at.startIndex) + item.insertText + inputBuffer.slice(cursorIndex);
284
+ cursorIndex = at.startIndex + item.insertText.length;
285
+ return true;
286
+ }
287
+ }
288
+ return false;
289
+ }
290
+ function finalizePromptOnSubmit(submittedText) {
291
+ const cols = Math.max(20, process.stdout.columns || 80);
292
+ const symbolStr = promptSymbol.includes("plan")
293
+ ? `${chalk.cyan.bold("plan ❯")} `
294
+ : `${chalk.bold.hex("#D97757")("❯")} `;
295
+ // Move cursor to top of frame
296
+ if (lastCursorLineIndex > 0) {
297
+ process.stdout.write(`\x1b[${lastCursorLineIndex}A\r`);
298
+ }
299
+ else {
300
+ process.stdout.write("\r");
301
+ }
302
+ // Draw clean submitted prompt box in terminal history
303
+ process.stdout.write(`\x1b[2K${chalk.gray("─".repeat(cols))}\x1b[1E`);
304
+ process.stdout.write(`\x1b[2K${symbolStr}${chalk.bold.white(submittedText)}\x1b[1E`);
305
+ process.stdout.write(`\x1b[2K${chalk.gray("─".repeat(cols))}\x1b[1E`);
306
+ // Clear any leftover rows (dropdown, old footer)
307
+ const extraLines = Math.max(0, lastRenderedLines.length - 3);
308
+ for (let i = 0; i < extraLines; i++) {
309
+ process.stdout.write(`\x1b[2K`);
310
+ if (i < extraLines - 1)
311
+ process.stdout.write("\x1b[1E");
312
+ }
313
+ // Move cursor directly below the bottom bar
314
+ if (extraLines > 0) {
315
+ process.stdout.write(`\x1b[${extraLines}A\r\x1b[1E\n`);
316
+ }
317
+ else {
318
+ process.stdout.write("\n");
319
+ }
320
+ }
321
+ function onKeypress(str, key) {
322
+ if (isFinished)
323
+ return;
324
+ const items = getDropdownItems();
325
+ // Enter key
326
+ if (key && (key.name === "return" || key.name === "enter")) {
327
+ // Multi-line continuation if ends with \
328
+ if (inputBuffer.endsWith("\\")) {
329
+ inputBuffer = inputBuffer.slice(0, -1) + "\n";
330
+ cursorIndex = inputBuffer.length;
331
+ render();
332
+ return;
333
+ }
334
+ if (items.length > 0 && (inputBuffer === "/" || getAtMention().active)) {
335
+ if (applySelectedDropdownItem()) {
336
+ render();
337
+ return;
338
+ }
339
+ }
340
+ appendHistory(inputBuffer);
341
+ cleanup();
342
+ finalizePromptOnSubmit(inputBuffer);
343
+ resolve(inputBuffer);
344
+ return;
345
+ }
346
+ // Shift+Tab: cambia el modo de permisos instantáneamente
347
+ const isShiftTab = Boolean(key && key.name === "tab" && key.shift) ||
348
+ str === "\x1b[Z" ||
349
+ Boolean(key && key.sequence === "\x1b[Z");
350
+ if (isShiftTab) {
351
+ cycleMode();
352
+ render();
353
+ return;
354
+ }
355
+ // Tab autocompletion
356
+ if (key && key.name === "tab") {
357
+ if (items.length > 0) {
358
+ if (applySelectedDropdownItem()) {
359
+ render();
360
+ return;
361
+ }
362
+ }
363
+ }
364
+ // Up arrow
365
+ if (key && key.name === "up") {
366
+ if (items.length > 0) {
367
+ selectedIndex = (selectedIndex - 1 + items.length) % items.length;
368
+ render();
369
+ return;
370
+ }
371
+ else if (history.length > 0 && historyIdx > 0) {
372
+ historyIdx--;
373
+ inputBuffer = history[historyIdx] || "";
374
+ cursorIndex = inputBuffer.length;
375
+ render();
376
+ return;
377
+ }
378
+ }
379
+ // Down arrow
380
+ if (key && key.name === "down") {
381
+ if (items.length > 0) {
382
+ selectedIndex = (selectedIndex + 1) % items.length;
383
+ render();
384
+ return;
385
+ }
386
+ else if (historyIdx < history.length - 1) {
387
+ historyIdx++;
388
+ inputBuffer = history[historyIdx] || "";
389
+ cursorIndex = inputBuffer.length;
390
+ render();
391
+ return;
392
+ }
393
+ else if (historyIdx === history.length - 1) {
394
+ historyIdx = history.length;
395
+ inputBuffer = "";
396
+ cursorIndex = 0;
397
+ render();
398
+ return;
399
+ }
400
+ }
401
+ // Left arrow
402
+ if (key && key.name === "left") {
403
+ if (cursorIndex > 0) {
404
+ cursorIndex--;
405
+ render();
406
+ }
407
+ return;
408
+ }
409
+ // Right arrow
410
+ if (key && key.name === "right") {
411
+ if (cursorIndex < inputBuffer.length) {
412
+ cursorIndex++;
413
+ render();
414
+ }
415
+ return;
416
+ }
417
+ // Backspace
418
+ if (key && (key.name === "backspace" || (key.ctrl && key.name === "h"))) {
419
+ if (cursorIndex > 0) {
420
+ inputBuffer = inputBuffer.slice(0, cursorIndex - 1) + inputBuffer.slice(cursorIndex);
421
+ cursorIndex--;
422
+ selectedIndex = 0;
423
+ render();
424
+ }
425
+ return;
426
+ }
427
+ // Ctrl + C / Esc to exit
428
+ if ((key && key.ctrl && key.name === "c") || (key && key.name === "escape" && inputBuffer.length === 0)) {
429
+ cleanup();
430
+ finalizePromptOnSubmit(inputBuffer);
431
+ process.exit(0);
432
+ }
433
+ // Esc when text is written: clear line
434
+ if (key && key.name === "escape" && inputBuffer.length > 0) {
435
+ inputBuffer = "";
436
+ cursorIndex = 0;
437
+ selectedIndex = 0;
438
+ render();
439
+ return;
440
+ }
441
+ // Ctrl + L (clear screen)
442
+ if (key && key.ctrl && key.name === "l") {
443
+ console.clear();
444
+ lastRenderedLines = [];
445
+ lastCursorLineIndex = 1;
446
+ render();
447
+ return;
448
+ }
449
+ // Printable characters or pasted text
450
+ if (str &&
451
+ !isShiftTab &&
452
+ str !== "\x1b[Z" &&
453
+ (!key || (!key.ctrl && !key.meta) || (key.ctrl && key.meta) || (!key.name && str.length > 0))) {
454
+ const cleanStr = str.replace(/[\r\n]/g, "");
455
+ if (cleanStr.length > 0) {
456
+ inputBuffer = inputBuffer.slice(0, cursorIndex) + cleanStr + inputBuffer.slice(cursorIndex);
457
+ cursorIndex += cleanStr.length;
458
+ selectedIndex = 0;
459
+ render();
460
+ }
461
+ }
462
+ }
463
+ process.stdin.on("keypress", onKeypress);
464
+ render();
465
+ });
466
+ }
@@ -0,0 +1,6 @@
1
+ export declare class ReasoningStream {
2
+ private hasStarted;
3
+ private atLineStart;
4
+ push(chunk: string): void;
5
+ flush(): void;
6
+ }
@@ -0,0 +1,38 @@
1
+ import chalk from "chalk";
2
+ import process from "node:process";
3
+ export class ReasoningStream {
4
+ hasStarted = false;
5
+ atLineStart = true;
6
+ push(chunk) {
7
+ if (!chunk)
8
+ return;
9
+ if (!this.hasStarted) {
10
+ this.hasStarted = true;
11
+ process.stdout.write(`\n ${chalk.bold.hex("#8B5CF6")("💭 Razonando:")}\n`);
12
+ }
13
+ for (let i = 0; i < chunk.length; i++) {
14
+ const char = chunk[i];
15
+ if (this.atLineStart) {
16
+ process.stdout.write(` ${chalk.hex("#8B5CF6")("│")} `);
17
+ this.atLineStart = false;
18
+ }
19
+ if (char === "\n") {
20
+ process.stdout.write("\n");
21
+ this.atLineStart = true;
22
+ }
23
+ else {
24
+ process.stdout.write(chalk.gray(char));
25
+ }
26
+ }
27
+ }
28
+ flush() {
29
+ if (this.hasStarted) {
30
+ if (!this.atLineStart) {
31
+ process.stdout.write("\n");
32
+ this.atLineStart = true;
33
+ }
34
+ process.stdout.write("\n");
35
+ this.hasStarted = false;
36
+ }
37
+ }
38
+ }
@@ -0,0 +1,10 @@
1
+ export declare function renderUserPrompt(text: string): void;
2
+ export declare function renderToolStart(name: string, summary: string): void;
3
+ export declare function renderToolSuccess(name: string, resultSummary: string): void;
4
+ export declare function renderToolError(name: string, errorSummary: string): void;
5
+ export declare function renderDiff(filePath: string, diffText: string): void;
6
+ export declare function renderReasoning(reasoningText: string): void;
7
+ export declare function renderAssistantResponse(markdownText: string): void;
8
+ export declare function renderFooter(costStr: string, outTokens: number, cachedTokens: number, modelName: string, durationMs?: number, contextPercent?: number, quotaStr?: string): void;
9
+ export declare function renderError(message: string): void;
10
+ export declare function formatToolName(name: string): string;
@@ -0,0 +1,142 @@
1
+ import chalk from "chalk";
2
+ import { Marked } from "marked";
3
+ import { markedTerminal } from "marked-terminal";
4
+ const marked = new Marked(markedTerminal({
5
+ heading: chalk.bold.hex("#D97757"),
6
+ firstHeading: chalk.bold.hex("#D97757").underline,
7
+ secondHeading: chalk.bold.hex("#FFA500"),
8
+ thirdHeading: chalk.bold.white,
9
+ code: chalk.cyan,
10
+ codespan: (text) => chalk.bgHex("#27272A").hex("#F4F4F5")(` ${text} `),
11
+ strong: chalk.bold.white,
12
+ em: chalk.italic.hex("#D97757"),
13
+ hr: () => chalk.gray("\n ──────────────────────────────────────────────────────────\n"),
14
+ unescape: true,
15
+ tableOptions: {
16
+ style: {
17
+ head: ["cyan", "bold"],
18
+ border: ["gray"],
19
+ },
20
+ },
21
+ }));
22
+ export function renderUserPrompt(text) {
23
+ const symbol = chalk.bold.hex("#D97757")("❯");
24
+ const shaded = chalk.bgHex("#242730").hex("#F4F4F5").bold(` ${text.trim()} `);
25
+ console.log(`\n${symbol} ${shaded}\n`);
26
+ }
27
+ // Tool call rendering as specified in §B5
28
+ export function renderToolStart(name, summary) {
29
+ const bullet = chalk.hex("#FFA500")("●"); // Amber while starting
30
+ const toolName = chalk.bold.white(formatToolName(name));
31
+ const argPart = summary ? chalk.gray(`(${summary.length > 60 ? summary.slice(0, 57) + '...' : summary})`) : "";
32
+ console.log(`\n${bullet} ${toolName}${argPart}`);
33
+ }
34
+ export function renderToolSuccess(name, resultSummary) {
35
+ const rawLines = resultSummary.trim().split("\n");
36
+ const maxVisibleLines = 5;
37
+ const visibleLines = rawLines.slice(0, maxVisibleLines);
38
+ const remainingCount = rawLines.length - maxVisibleLines;
39
+ const indented = visibleLines.map((line, idx) => {
40
+ const prefix = idx === 0 ? ` ${chalk.gray("⎿")} ` : " ";
41
+ return `${prefix}${colorizeText(line)}`;
42
+ }).join("\n");
43
+ console.log(indented);
44
+ if (remainingCount > 0) {
45
+ console.log(chalk.gray(` … +${remainingCount} líneas más`));
46
+ }
47
+ }
48
+ export function renderToolError(name, errorSummary) {
49
+ console.log(` ${chalk.red("⎿")} ${chalk.red(errorSummary)}`);
50
+ }
51
+ export function renderDiff(filePath, diffText) {
52
+ console.log(`\n${diffText}\n`);
53
+ }
54
+ export function renderReasoning(reasoningText) {
55
+ if (!reasoningText.trim())
56
+ return;
57
+ const lines = reasoningText.trim().split("\n");
58
+ const formatted = lines.map((l) => ` ${chalk.hex("#8B5CF6")("│")} ${chalk.gray(l)}`).join("\n");
59
+ console.log(`\n ${chalk.bold.hex("#8B5CF6")("💭 Razonamiento:")}\n${formatted}\n`);
60
+ }
61
+ export function renderAssistantResponse(markdownText) {
62
+ try {
63
+ // Aggressively clean triple asterisks, triple underscores, and excessive separators
64
+ let cleaned = markdownText
65
+ .replace(/\r\n/g, "\n")
66
+ .replace(/^\s*(\*\*\*|---|___|\*\s+\*\s+\*)\s*$/gm, "\n---\n")
67
+ .replace(/\n{3,}/g, "\n\n");
68
+ let parsed = String(marked.parse(cleaned));
69
+ // Convert raw asterisks bullets to terracotta bullets
70
+ parsed = parsed.replace(/^(\s*)\*\s+/gm, `$1${chalk.hex("#D97757")("•")} `);
71
+ console.log(`\n${parsed}`);
72
+ }
73
+ catch {
74
+ console.log(`\n${markdownText.trim()}\n`);
75
+ }
76
+ }
77
+ export function renderFooter(costStr, outTokens, cachedTokens, modelName, durationMs, contextPercent, quotaStr) {
78
+ const durationPart = durationMs ? `${(durationMs / 1000).toFixed(1)}s · ` : "";
79
+ const outPart = `${outTokens.toLocaleString()} tokens salida`;
80
+ const cachePart = cachedTokens > 0 ? ` (${(cachedTokens / 1000).toFixed(1)}k en caché)` : "";
81
+ const contextPart = contextPercent !== undefined ? ` · ${contextPercent}% contexto libre` : "";
82
+ const quotaPart = quotaStr ? ` · ${quotaStr}` : "";
83
+ const line = chalk.gray(` ${chalk.hex("#D97757")("⚡")} ${durationPart}${outPart}${cachePart} · ${chalk.green(costStr)} · ${modelName}${contextPart}${quotaPart}`);
84
+ console.log(`${line}\n`);
85
+ }
86
+ export function renderError(message) {
87
+ console.log(`\n ${chalk.bgRed.white.bold(" ERROR ")} ${chalk.red(message)}\n`);
88
+ }
89
+ function colorizeText(text) {
90
+ // Highlight file paths (C:\..., /path/...) in cyan
91
+ return text
92
+ .replace(/([a-zA-Z]:\\[^\s"']+)/g, (m) => chalk.cyan(m))
93
+ .replace(/(\b\w+\.(?:ts|js|json|png|py|md|html|css|tsx|jsx)\b)/g, (m) => chalk.bold.white(m));
94
+ }
95
+ export function formatToolName(name) {
96
+ switch (name) {
97
+ case "todo_write":
98
+ return "Task";
99
+ case "use_skill":
100
+ return "Skill";
101
+ case "save_memory":
102
+ return "MemorySave";
103
+ case "read_memory":
104
+ return "MemoryRead";
105
+ case "spawn_agent":
106
+ return "SubAgent";
107
+ case "browser_live":
108
+ return "BrowserLive";
109
+ case "computer_use":
110
+ return "ComputerUse";
111
+ case "change_dir":
112
+ return "Cd";
113
+ case "create_image":
114
+ return "CreateImage";
115
+ case "web_search":
116
+ return "SearchWeb";
117
+ case "find_files":
118
+ return "Glob";
119
+ case "view_file":
120
+ return "Read";
121
+ case "edit_file":
122
+ return "Edit";
123
+ case "write_file":
124
+ return "Write";
125
+ case "delete_file":
126
+ return "Delete";
127
+ case "move_copy_file":
128
+ return "Move";
129
+ case "run_command":
130
+ return "Bash";
131
+ case "list_dir":
132
+ return "ListDir";
133
+ case "grep_search":
134
+ return "Grep";
135
+ case "web_fetch":
136
+ return "WebFetch";
137
+ case "get_system_info":
138
+ return "SystemInfo";
139
+ default:
140
+ return name;
141
+ }
142
+ }