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 @@
1
+ export declare function writeFile(filePath: string, content: string, cwd?: string): Promise<string>;
@@ -0,0 +1,13 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ export async function writeFile(filePath, content, cwd = process.cwd()) {
4
+ const resolved = path.isAbsolute(filePath) ? filePath : path.resolve(cwd, filePath);
5
+ const dir = path.dirname(resolved);
6
+ if (!fs.existsSync(dir)) {
7
+ fs.mkdirSync(dir, { recursive: true });
8
+ }
9
+ const isNew = !fs.existsSync(resolved);
10
+ fs.writeFileSync(resolved, content, "utf-8");
11
+ const action = isNew ? "creado" : "sobreescrito";
12
+ return `✔ Archivo "${filePath}" ${action} exitosamente (${content.length} caracteres).`;
13
+ }
@@ -0,0 +1,10 @@
1
+ import { EspectroModel } from "../config/constants.js";
2
+ export type MascotState = "idle" | "blink" | "happy" | "thinking";
3
+ export declare const SILKSCREEN_TITLE_ROWS: string[];
4
+ export declare function colorizeSilkscreenLine(line: string): string;
5
+ export declare function renderMascotLines(isBlinking?: boolean): string[];
6
+ export declare function buildBannerContent(cwd: string, model: EspectroModel, isBlinking?: boolean): string;
7
+ export declare function showBanner(cwd: string, model: EspectroModel, state?: MascotState): void;
8
+ export declare function animateStartupBanner(cwd: string, model: EspectroModel): Promise<void>;
9
+ export declare function showEffortMenu(currentId: string): void;
10
+ export declare function showHelp(): void;
@@ -0,0 +1,165 @@
1
+ import chalk from "chalk";
2
+ import { ESPECTRO_MODELS } from "../config/constants.js";
3
+ import { CONTEXT_WINDOW } from "../config/constants.js";
4
+ // Base grid for mascota-pixeleada: 16 cols x 16 rows
5
+ const BASE_GRID_16X16 = [
6
+ [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
7
+ [1, 3, 1, 0, 0, 0, 0, 0, 0, 0, 1, 3, 1, 0, 0, 0],
8
+ [1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 0, 0, 0],
9
+ [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0],
10
+ [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0],
11
+ [1, 1, 4, 4, 1, 1, 1, 1, 4, 4, 1, 1, 1, 0, 0, 0], // Row 5: Eyes Upper
12
+ [1, 1, 4, 4, 1, 1, 1, 1, 4, 4, 1, 1, 1, 0, 0, 0], // Row 6: Eyes Lower
13
+ [1, 3, 3, 1, 1, 4, 4, 1, 1, 3, 3, 1, 1, 0, 0, 0], // Row 7: Cheeks + Nose
14
+ [0, 1, 3, 3, 3, 3, 3, 3, 3, 3, 1, 0, 1, 1, 0, 0], // Row 8: Muzzle
15
+ [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 5, 5, 0], // Row 9: Body + Tail
16
+ [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 5, 0], // Row 10: Body + Tail
17
+ [0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0], // Row 11: 4 Legs
18
+ [0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0], // Row 12
19
+ [0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0], // Row 13
20
+ [0, 2, 2, 0, 2, 2, 0, 2, 2, 0, 2, 2, 0, 0, 0, 0], // Row 14: Feet
21
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
22
+ ];
23
+ const COLOR_MAP = {
24
+ 0: null,
25
+ 1: "#D97757", // Terracotta
26
+ 2: "#C06345", // Darker feet
27
+ 3: "#F5C7B2", // Peach / inner ear
28
+ 4: "#1C1917", // Black eyes / nose
29
+ 5: "#FAF5F0", // White tail
30
+ 6: "#A855F7", // Purple sparkle
31
+ };
32
+ // Exact Mascot Holographic Palette from Sticker:
33
+ const HOLO_PALETTE = ["#E4CDEC", "#FFC7C7", "#F6E0B4", "#FDFFDE", "#C5E4B5", "#A4C5BF"];
34
+ // Complete 3D Silkscreen Pixel Typography for "CHOCOLATITO CODE"
35
+ export const SILKSCREEN_TITLE_ROWS = [
36
+ "█▀▀█ █ █ █▀▀█ █▀▀█ █▀▀█ █ █▀▀█ ▀▀█▀▀ ▀█▀ ▀▀█▀▀ █▀▀█ █▀▀█ █▀▀█ █▀▀▄ █▀▀▀",
37
+ "█ █▀▀█ █ █ █ █ █ █ █▄▄█ █ █ █ █ █ █ █ █ █ █ █▀▀▀",
38
+ "█▄▄█ █ █ █▄▄█ █▄▄█ █▄▄█ █▄▄█ █ █ █ ▄█▄ █ █▄▄█ █▄▄█ █▄▄█ █▄▄▀ █▄▄▄",
39
+ ];
40
+ export function colorizeSilkscreenLine(line) {
41
+ let out = "";
42
+ for (let i = 0; i < line.length; i++) {
43
+ const char = line[i];
44
+ if (char === " ") {
45
+ out += " ";
46
+ continue;
47
+ }
48
+ const t = i / line.length;
49
+ const colorIdx = Math.min(HOLO_PALETTE.length - 1, Math.floor(t * HOLO_PALETTE.length));
50
+ out += chalk.hex(HOLO_PALETTE[colorIdx]).bold(char);
51
+ }
52
+ return out;
53
+ }
54
+ export function renderMascotLines(isBlinking = false) {
55
+ const grid = BASE_GRID_16X16.map((r) => [...r]);
56
+ if (isBlinking) {
57
+ grid[5][2] = 1;
58
+ grid[5][3] = 1;
59
+ grid[5][8] = 1;
60
+ grid[5][9] = 1;
61
+ grid[6][2] = 4;
62
+ grid[6][3] = 4;
63
+ grid[6][8] = 4;
64
+ grid[6][9] = 4;
65
+ }
66
+ const lines = [];
67
+ for (let y = 0; y < 16; y += 2) {
68
+ const topRow = grid[y];
69
+ const botRow = grid[y + 1];
70
+ let lineStr = "";
71
+ for (let x = 0; x < 16; x++) {
72
+ const topColor = COLOR_MAP[topRow[x]];
73
+ const botColor = COLOR_MAP[botRow[x]];
74
+ if (!topColor && !botColor) {
75
+ lineStr += " ";
76
+ }
77
+ else if (topColor && !botColor) {
78
+ lineStr += chalk.hex(topColor)("▀");
79
+ }
80
+ else if (!topColor && botColor) {
81
+ lineStr += chalk.hex(botColor)("▄");
82
+ }
83
+ else if (topColor && botColor) {
84
+ if (topColor === botColor) {
85
+ lineStr += chalk.hex(topColor)("█");
86
+ }
87
+ else {
88
+ lineStr += chalk.hex(topColor).bgHex(botColor)("▀");
89
+ }
90
+ }
91
+ }
92
+ lines.push(` ${lineStr} `);
93
+ }
94
+ return lines;
95
+ }
96
+ export function buildBannerContent(cwd, model, isBlinking = false) {
97
+ const mascotLines = renderMascotLines(isBlinking);
98
+ const rightLines = [
99
+ colorizeSilkscreenLine(SILKSCREEN_TITLE_ROWS[0]),
100
+ colorizeSilkscreenLine(SILKSCREEN_TITLE_ROWS[1]),
101
+ colorizeSilkscreenLine(SILKSCREEN_TITLE_ROWS[2]),
102
+ chalk.gray("─".repeat(74)),
103
+ `${chalk.gray("Motor:")} ${chalk.bold.white(model.name)} · ${chalk.hex("#A4C5BF")("Espectro Engine")}`,
104
+ `${chalk.gray("Carpeta:")} ${chalk.cyan(cwd)}`,
105
+ `${chalk.gray("Memoria:")} ${chalk.bold.green(`${(CONTEXT_WINDOW / 1000).toLocaleString("es")}k tokens`)} ${chalk.gray("(Prompt Caching)")}`,
106
+ `${chalk.gray("Escribe")} ${chalk.hex("#A855F7")("/")} ${chalk.gray("para comandos,")} ${chalk.hex("#A4C5BF")("@")} ${chalk.gray("para archivos, o")} ${chalk.hex("#E4CDEC")("/effort")}${chalk.gray(".")}`,
107
+ ];
108
+ const combinedRows = [];
109
+ for (let i = 0; i < 8; i++) {
110
+ const left = mascotLines[i] || " ";
111
+ combinedRows.push(`${left}${rightLines[i]}`);
112
+ }
113
+ return ["", ...combinedRows, ""].join("\n");
114
+ }
115
+ export function showBanner(cwd, model, state = "idle") {
116
+ const content = buildBannerContent(cwd, model, state === "blink");
117
+ console.log(content);
118
+ }
119
+ export async function animateStartupBanner(cwd, model) {
120
+ // Impresión limpia instantánea sin animaciones lentas en el terminal
121
+ showBanner(cwd, model, "idle");
122
+ }
123
+ export function showEffortMenu(currentId) {
124
+ const items = Object.values(ESPECTRO_MODELS).map((m) => {
125
+ const isCurrent = m.id === currentId;
126
+ const bullet = isCurrent ? chalk.hex("#A855F7")("●") : chalk.gray("○");
127
+ const title = isCurrent ? chalk.bold.hex("#D97757")(m.name) : chalk.bold.white(m.name);
128
+ const tag = isCurrent ? chalk.bgHex("#A855F7").black.bold(" ACTIVO ") : "";
129
+ return ` ${bullet} ${title} ${chalk.gray(`[Esfuerzo: ${m.effortLevel}]`)} ${tag}\n ${chalk.gray(m.description)}\n ${chalk.gray("Uso:")} ${chalk.hex("#A855F7")(`/effort ${m.id}`)}`;
130
+ });
131
+ console.log([
132
+ "",
133
+ chalk.bold.hex("#E4CDEC")("POTENCIA DEL MOTOR — CHOCOLATITO CODE"),
134
+ "",
135
+ ...items,
136
+ "",
137
+ chalk.italic.gray(" Selecciona con /effort 1.0, 1.5, 2.0, 2.5 según la dificultad del problema."),
138
+ "",
139
+ ].join("\n"));
140
+ }
141
+ export function showHelp() {
142
+ console.log([
143
+ "",
144
+ chalk.bold.hex("#E4CDEC")("GUÍA DE COMANDOS — CHOCOLATITO CODE"),
145
+ "",
146
+ ` ${chalk.hex("#A855F7")("/plan")} Alterna Modo Plan (solo lectura y diseño de arquitectura)`,
147
+ ` ${chalk.hex("#A855F7")("/resume [id]")} Reanuda una sesión de trabajo guardada`,
148
+ ` ${chalk.hex("#A855F7")("/skills")} Lista los skills disponibles bajo demanda`,
149
+ ` ${chalk.hex("#A855F7")("/memory")} Muestra el índice de memoria persistente`,
150
+ ` ${chalk.hex("#A855F7")("/goal <meta>")} Ejecuta una meta compleja con auto-curación`,
151
+ ` ${chalk.hex("#A855F7")("/effort [nivel]")} Cambia la potencia del motor (1.0, 1.5, 2.0, 2.5)`,
152
+ ` ${chalk.hex("#A855F7")("/compact")} Comprime y optimiza la memoria del chat`,
153
+ ` ${chalk.hex("#A855F7")("/undo")} Deshace el último cambio de archivos en la sesión`,
154
+ ` ${chalk.hex("#A855F7")("/export [doc.md]")} Exporta la bitácora de la sesión a Markdown`,
155
+ ` ${chalk.hex("#A855F7")("/diff")} Muestra diferencias de código en Git`,
156
+ ` ${chalk.hex("#A855F7")("/commit <msg>")} Crea un commit en Git`,
157
+ ` ${chalk.hex("#A855F7")("/cd <ruta>")} Cambia de directorio con búsqueda profunda`,
158
+ ` ${chalk.hex("#A855F7")("/cost")} Muestra el resumen de tokens y costo en USD`,
159
+ ` ${chalk.hex("#A855F7")("/init")} Crea el archivo ${chalk.yellow("CHOCOLATITO.md")} de directivas`,
160
+ ` ${chalk.hex("#A855F7")("/clear")} Limpia el historial de la conversación`,
161
+ ` ${chalk.hex("#A855F7")("/help")} Muestra esta ayuda`,
162
+ ` ${chalk.hex("#A855F7")("/exit")} Cierra la sesión`,
163
+ "",
164
+ ].join("\n"));
165
+ }
@@ -0,0 +1 @@
1
+ export declare function formatNumberedDiff(filePath: string, oldText: string, newText: string, startLine?: number): string;
@@ -0,0 +1,60 @@
1
+ import chalk from "chalk";
2
+ import { diffLines } from "diff";
3
+ export function formatNumberedDiff(filePath, oldText, newText, startLine = 1) {
4
+ const changes = diffLines(oldText, newText);
5
+ const lines = [];
6
+ lines.push(` ${chalk.gray("⎿")} ${chalk.bold.white(filePath)} ${chalk.gray(`(L${startLine}+)`)}`);
7
+ lines.push("");
8
+ let oldLineNum = startLine;
9
+ let newLineNum = startLine;
10
+ for (const part of changes) {
11
+ const rawLines = part.value.replace(/\r\n/g, "\n").split("\n");
12
+ if (rawLines[rawLines.length - 1] === "")
13
+ rawLines.pop();
14
+ if (part.added) {
15
+ for (const line of rawLines) {
16
+ const numStr = String(newLineNum).padStart(6, " ");
17
+ lines.push(` ${chalk.gray(numStr)} ${chalk.green("+")} ${chalk.bgHex("#1E382B").hex("#A3E635")(line)}`);
18
+ newLineNum++;
19
+ }
20
+ }
21
+ else if (part.removed) {
22
+ for (const line of rawLines) {
23
+ const numStr = String(oldLineNum).padStart(6, " ");
24
+ lines.push(` ${chalk.gray(numStr)} ${chalk.red("-")} ${chalk.bgHex("#3B1D22").hex("#FCA5A5")(line)}`);
25
+ oldLineNum++;
26
+ }
27
+ }
28
+ else {
29
+ // Unchanged context (show first 2 and last 2 if too long)
30
+ if (rawLines.length > 6) {
31
+ const firstTwo = rawLines.slice(0, 2);
32
+ const lastTwo = rawLines.slice(-2);
33
+ for (const line of firstTwo) {
34
+ const numStr = String(newLineNum).padStart(6, " ");
35
+ lines.push(` ${chalk.gray(numStr)} ${chalk.gray(line)}`);
36
+ oldLineNum++;
37
+ newLineNum++;
38
+ }
39
+ lines.push(` ${chalk.gray(" ...")} ${chalk.gray(`(${rawLines.length - 4} líneas sin cambios)`)}`);
40
+ oldLineNum += rawLines.length - 4;
41
+ newLineNum += rawLines.length - 4;
42
+ for (const line of lastTwo) {
43
+ const numStr = String(newLineNum).padStart(6, " ");
44
+ lines.push(` ${chalk.gray(numStr)} ${chalk.gray(line)}`);
45
+ oldLineNum++;
46
+ newLineNum++;
47
+ }
48
+ }
49
+ else {
50
+ for (const line of rawLines) {
51
+ const numStr = String(newLineNum).padStart(6, " ");
52
+ lines.push(` ${chalk.gray(numStr)} ${chalk.gray(line)}`);
53
+ oldLineNum++;
54
+ newLineNum++;
55
+ }
56
+ }
57
+ }
58
+ }
59
+ return lines.join("\n");
60
+ }
@@ -0,0 +1,17 @@
1
+ /** Suelta el teclado de cualquier vigilante que lo tenga. Lo llama el prompt. */
2
+ export declare function releaseKeyboard(): void;
3
+ export declare class InterruptWatcher {
4
+ private controller;
5
+ private wasRaw;
6
+ private listening;
7
+ private onKey;
8
+ private aborted;
9
+ private ctrlCCount;
10
+ get interrupted(): boolean;
11
+ get signal(): AbortSignal | undefined;
12
+ start(): AbortSignal | undefined;
13
+ private abort;
14
+ stop(): void;
15
+ }
16
+ /** Un error de aborto no es un fallo: es el usuario diciendo "para". */
17
+ export declare function isAbortError(err: any): boolean;
@@ -0,0 +1,156 @@
1
+ import readline from "node:readline";
2
+ import process from "node:process";
3
+ import chalk from "chalk";
4
+ import { cycleMode, modeAnnouncement } from "./modes.js";
5
+ import { pushTyped, backspaceTyped, commitTyped } from "./typeAhead.js";
6
+ /**
7
+ * Interrupcion con Esc mientras el agente trabaja.
8
+ *
9
+ * El spinner llevaba tiempo anunciando "esc para pausar", pero no habia nada
10
+ * detras: la tecla no hacia nada y la unica salida era matar el proceso con
11
+ * Ctrl+C, perdiendo la sesion entera. Prometer un atajo que no existe es peor
12
+ * que no ofrecerlo.
13
+ *
14
+ * Aqui se escucha el teclado solo mientras dura la peticion, y se aborta la
15
+ * llamada HTTP de verdad (no se deja corriendo de fondo gastando tokens).
16
+ *
17
+ * El teclado sigue siendo del usuario mientras tanto: Shift+Tab cambia el modo
18
+ * de permisos sin esperar a que acabe el turno, y lo que escriba se acumula y se
19
+ * encola en vez de perderse. Antes, todo lo que no fuera Esc o Ctrl+C se tiraba
20
+ * en silencio y parecia que el terminal se habia colgado.
21
+ */
22
+ /**
23
+ * Vigilantes con el teclado cogido ahora mismo.
24
+ *
25
+ * Deberia haber como mucho uno, pero el bucle tiene muchas salidas (error del
26
+ * motor, interrupcion, hook que deniega, permiso rechazado) y basta que una se
27
+ * escape sin parar el suyo para que el teclado se quede en manos de nadie: el
28
+ * prompt siguiente se dibuja, el usuario teclea y no aparece nada.
29
+ *
30
+ * En vez de confiar en que las 20 salidas esten bien, el prompt reclama el
31
+ * teclado antes de abrirse.
32
+ */
33
+ const activos = new Set();
34
+ /** Suelta el teclado de cualquier vigilante que lo tenga. Lo llama el prompt. */
35
+ export function releaseKeyboard() {
36
+ for (const w of Array.from(activos)) {
37
+ w.stop();
38
+ }
39
+ }
40
+ export class InterruptWatcher {
41
+ controller = null;
42
+ wasRaw = false;
43
+ listening = false;
44
+ onKey = null;
45
+ aborted = false;
46
+ ctrlCCount = 0;
47
+ get interrupted() {
48
+ return this.aborted;
49
+ }
50
+ get signal() {
51
+ return this.controller?.signal;
52
+ }
53
+ start() {
54
+ if (this.listening)
55
+ return this.controller?.signal;
56
+ this.controller = new AbortController();
57
+ this.aborted = false;
58
+ this.ctrlCCount = 0;
59
+ // Sin terminal interactiva (modo `chocolatito "tarea"`, CI, tuberias) no hay
60
+ // teclado que escuchar: se devuelve la senal y ya.
61
+ if (!process.stdin.isTTY || !process.stdin.setRawMode) {
62
+ return this.controller.signal;
63
+ }
64
+ readline.emitKeypressEvents(process.stdin);
65
+ this.wasRaw = process.stdin.isRaw || false;
66
+ process.stdin.setRawMode(true);
67
+ process.stdin.resume();
68
+ this.onKey = (str, key) => {
69
+ // 1. Shift+Tab: cambiar de modo de permisos sin esperar al final del turno.
70
+ const isShiftTab = Boolean(key && key.name === "tab" && key.shift) ||
71
+ str === "\x1b[Z" ||
72
+ Boolean(key && key.sequence === "\x1b[Z");
73
+ if (isShiftTab) {
74
+ cycleMode();
75
+ process.stdout.write(`\r\x1b[2K${modeAnnouncement()}\n`);
76
+ return;
77
+ }
78
+ // Ignorar secuencias de escape multi-byte (flechas, inicio, fin, etc.) para que no aborten
79
+ if (key && (key.sequence?.startsWith("\x1b[") || key.sequence?.startsWith("\x1bO"))) {
80
+ return;
81
+ }
82
+ // 2. Solo la tecla Escape explícita aborta la tarea
83
+ if (key && key.name === "escape" && (str === "\x1b" || !str)) {
84
+ this.abort();
85
+ return;
86
+ }
87
+ // Enter durante la generacion: lo escrito queda en cola como siguiente
88
+ // orden, en vez de perderse.
89
+ if (key && (key.name === "return" || key.name === "enter")) {
90
+ const texto = commitTyped();
91
+ if (texto) {
92
+ const corto = texto.length > 70 ? `${texto.slice(0, 67)}...` : texto;
93
+ process.stdout.write(`\r\x1b[2K${chalk.gray("↳ en cola:")} ${chalk.white(corto)}\n`);
94
+ }
95
+ return;
96
+ }
97
+ if (key && (key.name === "backspace" || (key.ctrl && key.name === "h"))) {
98
+ backspaceTyped();
99
+ return;
100
+ }
101
+ // El primer Ctrl+C interrumpe la tarea; el segundo cierra el programa.
102
+ // Asi no se pierde la sesion por querer parar una sola accion.
103
+ if (key && key.ctrl && key.name === "c") {
104
+ this.ctrlCCount++;
105
+ if (this.ctrlCCount === 1) {
106
+ this.abort();
107
+ }
108
+ else {
109
+ this.stop();
110
+ process.stdout.write("\n");
111
+ process.exit(0);
112
+ }
113
+ return;
114
+ }
115
+ // Todo lo demas es texto: se guarda para el siguiente turno. El spinner lo
116
+ // va pintando debajo segun se escribe.
117
+ if (str && !isShiftTab && str !== "\x1b[Z" && (!key || (!key.ctrl && !key.meta))) {
118
+ pushTyped(str);
119
+ }
120
+ };
121
+ process.stdin.on("keypress", this.onKey);
122
+ this.listening = true;
123
+ activos.add(this);
124
+ return this.controller.signal;
125
+ }
126
+ abort() {
127
+ if (this.aborted)
128
+ return;
129
+ this.aborted = true;
130
+ try {
131
+ this.controller?.abort();
132
+ }
133
+ catch { }
134
+ }
135
+ stop() {
136
+ if (this.onKey) {
137
+ process.stdin.removeListener("keypress", this.onKey);
138
+ this.onKey = null;
139
+ }
140
+ if (this.listening && process.stdin.setRawMode) {
141
+ // Se devuelve el terminal como estaba, o el prompt siguiente queda roto.
142
+ process.stdin.setRawMode(this.wasRaw);
143
+ }
144
+ this.listening = false;
145
+ activos.delete(this);
146
+ }
147
+ }
148
+ /** Un error de aborto no es un fallo: es el usuario diciendo "para". */
149
+ export function isAbortError(err) {
150
+ const name = String(err?.name || "");
151
+ const msg = String(err?.message || "").toLowerCase();
152
+ return (name === "AbortError" ||
153
+ name === "APIUserAbortError" ||
154
+ msg.includes("aborted") ||
155
+ msg.includes("request was aborted"));
156
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Formatea UNA linea de markdown ya completa.
3
+ *
4
+ * Es pura y exportada para poder comprobarla: lo que se ve en el terminal es
5
+ * dificil de revisar a ojo, y las reglas se pisan entre si con facilidad.
6
+ */
7
+ export declare function formatLine(line: string): string;
8
+ /**
9
+ * Va recibiendo los trozos del stream y escribe lineas ya formateadas.
10
+ *
11
+ * Guarda el resto incompleto hasta que llegue su salto de linea, y lleva la
12
+ * cuenta de si estamos dentro de un bloque de codigo, donde no se formatea nada.
13
+ */
14
+ export declare class MarkdownStream {
15
+ private pendiente;
16
+ private enBloqueCodigo;
17
+ private write;
18
+ constructor(write?: (s: string) => void);
19
+ push(chunk: string): void;
20
+ /** Lo que quede sin salto de linea al acabar el mensaje. */
21
+ flush(): void;
22
+ private renderLinea;
23
+ }
Binary file
@@ -0,0 +1,28 @@
1
+ /**
2
+ * MODO DE PERMISOS INTERACTIVO
3
+ *
4
+ * El usuario tiene que poder cambiar de modo con Shift+Tab en cualquier momento,
5
+ * tambien a mitad de una generacion. Antes solo se podia antes de escribir la
6
+ * orden, y una vez lanzada la tarea no quedaba mas remedio que contestar cada
7
+ * dialogo uno a uno hasta el final.
8
+ *
9
+ * El estado vive aqui, fuera del bucle del agente, porque lo tocan tres sitios
10
+ * distintos: el prompt, el vigilante de teclas mientras se genera, y el propio
11
+ * dialogo de permiso.
12
+ */
13
+ export type InteractiveMode = "default" | "acceptEdits" | "bypass";
14
+ export declare function getMode(): InteractiveMode;
15
+ export declare function setMode(mode: InteractiveMode): InteractiveMode;
16
+ /** Shift+Tab: normal -> acepta ediciones -> automatico -> normal. */
17
+ export declare function cycleMode(): InteractiveMode;
18
+ export declare function onModeChange(cb: (m: InteractiveMode) => void): void;
19
+ export interface ModeLook {
20
+ label: string;
21
+ hint: string;
22
+ color: string;
23
+ }
24
+ export declare function describeMode(mode?: InteractiveMode): ModeLook;
25
+ /** Etiqueta de una linea para el pie del prompt y para el spinner. */
26
+ export declare function modeBadge(mode?: InteractiveMode): string;
27
+ /** Aviso completo, para cuando el usuario acaba de cambiar de modo. */
28
+ export declare function modeAnnouncement(mode?: InteractiveMode): string;
@@ -0,0 +1,57 @@
1
+ import chalk from "chalk";
2
+ const ORDER = ["default", "acceptEdits", "bypass"];
3
+ let current = "default";
4
+ const listeners = [];
5
+ export function getMode() {
6
+ return current;
7
+ }
8
+ export function setMode(mode) {
9
+ current = mode;
10
+ for (const cb of listeners) {
11
+ try {
12
+ cb(current);
13
+ }
14
+ catch { }
15
+ }
16
+ return current;
17
+ }
18
+ /** Shift+Tab: normal -> acepta ediciones -> automatico -> normal. */
19
+ export function cycleMode() {
20
+ const idx = ORDER.indexOf(current);
21
+ return setMode(ORDER[(idx + 1) % ORDER.length]);
22
+ }
23
+ export function onModeChange(cb) {
24
+ listeners.push(cb);
25
+ }
26
+ const LOOKS = {
27
+ default: {
28
+ label: "normal",
29
+ hint: "pregunta antes de cada accion con consecuencias",
30
+ color: "#8B8B8B",
31
+ },
32
+ acceptEdits: {
33
+ label: "acepta ediciones",
34
+ hint: "escribe archivos sin preguntar; sigue preguntando por comandos y por el control del ordenador",
35
+ color: "#22C55E",
36
+ },
37
+ bypass: {
38
+ label: "automatico",
39
+ hint: "no pregunta nada, ni comandos ni control del ordenador",
40
+ color: "#EF4444",
41
+ },
42
+ };
43
+ export function describeMode(mode = current) {
44
+ return LOOKS[mode];
45
+ }
46
+ /** Etiqueta de una linea para el pie del prompt y para el spinner. */
47
+ export function modeBadge(mode = current) {
48
+ if (mode === "default")
49
+ return "";
50
+ const look = LOOKS[mode];
51
+ return chalk.hex(look.color)(`⇥ modo ${look.label}`);
52
+ }
53
+ /** Aviso completo, para cuando el usuario acaba de cambiar de modo. */
54
+ export function modeAnnouncement(mode = current) {
55
+ const look = LOOKS[mode];
56
+ return `${chalk.bold.hex(look.color)(`⇥ modo ${look.label}`)} ${chalk.gray(`· ${look.hint}`)} ${chalk.gray("· shift+tab para cambiar")}`;
57
+ }
@@ -0,0 +1,35 @@
1
+ export interface PermissionPromptResult {
2
+ approved: boolean;
3
+ rememberPattern?: boolean;
4
+ userFeedback?: string;
5
+ }
6
+ export declare function setAutoApprove(value: boolean): void;
7
+ /**
8
+ * DIALOGO DE PERMISO
9
+ *
10
+ * La version anterior tenia dos fallos visibles en cuanto se usaba:
11
+ *
12
+ * 1. Al mover la seleccion subia el cursor 12 lineas fijas, pero la caja mide
13
+ * entre 10 y 14 segun cuantos detalles tenga. Cada flecha dejaba media caja
14
+ * huerfana en pantalla, y por eso salian marcos vacios apilados.
15
+ *
16
+ * 2. El titulo llevaba un simbolo de anchura ambigua. El terminal lo pinta
17
+ * ocupando dos columnas y el calculo del ancho contaba una, asi que el borde
18
+ * derecho quedaba descuadrado justo en esa linea.
19
+ *
20
+ * Aqui se dibuja la caja a mano, con anchura conocida, y se repinta subiendo
21
+ * exactamente las lineas que se escribieron.
22
+ */
23
+ export declare function askToolPermission(toolName: string, args: Record<string, any>, cwd?: string): Promise<PermissionPromptResult>;
24
+ export declare const PERMISSION_OPTIONS: {
25
+ label: string;
26
+ hint: string;
27
+ }[];
28
+ /**
29
+ * Construye el dialogo entero como lista de lineas.
30
+ *
31
+ * Es una funcion pura a proposito: asi se puede comprobar en una prueba que
32
+ * todas las lineas del marco miden exactamente lo mismo. El descuadre del borde
33
+ * derecho no se ve leyendo el codigo, solo ejecutandolo.
34
+ */
35
+ export declare function buildPermissionBox(toolName: string, args: Record<string, any>, cwd: string, selectedIndex: number, width?: number): string[];