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.
- package/.mcp.json.ejemplo +19 -0
- package/COMPUTER-USE.md +484 -0
- package/LICENSE +21 -0
- package/README.md +186 -0
- package/dist/agent/context.d.ts +9 -0
- package/dist/agent/context.js +123 -0
- package/dist/agent/fileTracker.d.ts +16 -0
- package/dist/agent/fileTracker.js +53 -0
- package/dist/agent/loop.d.ts +25 -0
- package/dist/agent/loop.js +524 -0
- package/dist/agent/mentions.d.ts +11 -0
- package/dist/agent/mentions.js +43 -0
- package/dist/agent/retry.d.ts +17 -0
- package/dist/agent/retry.js +57 -0
- package/dist/agent/subagent.d.ts +2 -0
- package/dist/agent/subagent.js +116 -0
- package/dist/agent/syntaxValidator.d.ts +5 -0
- package/dist/agent/syntaxValidator.js +71 -0
- package/dist/agent/tracker.d.ts +19 -0
- package/dist/agent/tracker.js +38 -0
- package/dist/agent/undoManager.d.ts +37 -0
- package/dist/agent/undoManager.js +95 -0
- package/dist/config/constants.d.ts +77 -0
- package/dist/config/constants.js +146 -0
- package/dist/config/engine.d.ts +49 -0
- package/dist/config/engine.js +86 -0
- package/dist/config/env.d.ts +13 -0
- package/dist/config/env.js +103 -0
- package/dist/config/license.d.ts +39 -0
- package/dist/config/license.js +306 -0
- package/dist/config/permissions.d.ts +38 -0
- package/dist/config/permissions.js +239 -0
- package/dist/config/quota.d.ts +58 -0
- package/dist/config/quota.js +156 -0
- package/dist/hooks/manager.d.ts +60 -0
- package/dist/hooks/manager.js +193 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +470 -0
- package/dist/mcp/client.d.ts +55 -0
- package/dist/mcp/client.js +282 -0
- package/dist/mcp/manager.d.ts +26 -0
- package/dist/mcp/manager.js +167 -0
- package/dist/memory/manager.d.ts +9 -0
- package/dist/memory/manager.js +59 -0
- package/dist/prompts/systemPrompt.d.ts +5 -0
- package/dist/prompts/systemPrompt.js +177 -0
- package/dist/sessions/manager.d.ts +21 -0
- package/dist/sessions/manager.js +54 -0
- package/dist/skills/manager.d.ts +14 -0
- package/dist/skills/manager.js +86 -0
- package/dist/tools/browserCdp.d.ts +26 -0
- package/dist/tools/browserCdp.js +610 -0
- package/dist/tools/browserExtension.d.ts +58 -0
- package/dist/tools/browserExtension.js +479 -0
- package/dist/tools/browserLive.d.ts +12 -0
- package/dist/tools/browserLive.js +265 -0
- package/dist/tools/changeDir.d.ts +5 -0
- package/dist/tools/changeDir.js +63 -0
- package/dist/tools/computerUse.d.ts +58 -0
- package/dist/tools/computerUse.js +567 -0
- package/dist/tools/createImage.d.ts +1 -0
- package/dist/tools/createImage.js +34 -0
- package/dist/tools/definitions.d.ts +8 -0
- package/dist/tools/definitions.js +401 -0
- package/dist/tools/deleteFile.d.ts +1 -0
- package/dist/tools/deleteFile.js +17 -0
- package/dist/tools/editFile.d.ts +5 -0
- package/dist/tools/editFile.js +88 -0
- package/dist/tools/findFiles.d.ts +1 -0
- package/dist/tools/findFiles.js +33 -0
- package/dist/tools/getSystemInfo.d.ts +1 -0
- package/dist/tools/getSystemInfo.js +23 -0
- package/dist/tools/gitTools.d.ts +3 -0
- package/dist/tools/gitTools.js +47 -0
- package/dist/tools/grepSearch.d.ts +1 -0
- package/dist/tools/grepSearch.js +37 -0
- package/dist/tools/listDir.d.ts +1 -0
- package/dist/tools/listDir.js +33 -0
- package/dist/tools/moveCopyFile.d.ts +1 -0
- package/dist/tools/moveCopyFile.js +21 -0
- package/dist/tools/runCommand.d.ts +1 -0
- package/dist/tools/runCommand.js +61 -0
- package/dist/tools/runner.d.ts +5 -0
- package/dist/tools/runner.js +177 -0
- package/dist/tools/safety.d.ts +17 -0
- package/dist/tools/safety.js +33 -0
- package/dist/tools/todoTool.d.ts +8 -0
- package/dist/tools/todoTool.js +27 -0
- package/dist/tools/toolDefsComputer.d.ts +13 -0
- package/dist/tools/toolDefsComputer.js +205 -0
- package/dist/tools/truncator.d.ts +11 -0
- package/dist/tools/truncator.js +24 -0
- package/dist/tools/viewFile.d.ts +1 -0
- package/dist/tools/viewFile.js +45 -0
- package/dist/tools/visionBridge.d.ts +37 -0
- package/dist/tools/visionBridge.js +107 -0
- package/dist/tools/webFetch.d.ts +1 -0
- package/dist/tools/webFetch.js +25 -0
- package/dist/tools/webSearch.d.ts +1 -0
- package/dist/tools/webSearch.js +35 -0
- package/dist/tools/win/hostClient.d.ts +31 -0
- package/dist/tools/win/hostClient.js +158 -0
- package/dist/tools/win/hostScript.d.ts +1 -0
- package/dist/tools/win/hostScript.js +925 -0
- package/dist/tools/writeFile.d.ts +1 -0
- package/dist/tools/writeFile.js +13 -0
- package/dist/ui/banner.d.ts +10 -0
- package/dist/ui/banner.js +165 -0
- package/dist/ui/diffViewer.d.ts +1 -0
- package/dist/ui/diffViewer.js +60 -0
- package/dist/ui/interrupt.d.ts +17 -0
- package/dist/ui/interrupt.js +156 -0
- package/dist/ui/markdownStream.d.ts +23 -0
- package/dist/ui/markdownStream.js +0 -0
- package/dist/ui/modes.d.ts +28 -0
- package/dist/ui/modes.js +57 -0
- package/dist/ui/permissionPrompt.d.ts +35 -0
- package/dist/ui/permissionPrompt.js +271 -0
- package/dist/ui/prompt.d.ts +7 -0
- package/dist/ui/prompt.js +466 -0
- package/dist/ui/reasoningStream.d.ts +6 -0
- package/dist/ui/reasoningStream.js +38 -0
- package/dist/ui/renderer.d.ts +10 -0
- package/dist/ui/renderer.js +142 -0
- package/dist/ui/spinner.d.ts +24 -0
- package/dist/ui/spinner.js +111 -0
- package/dist/ui/theme.d.ts +36 -0
- package/dist/ui/theme.js +37 -0
- package/dist/ui/typeAhead.d.ts +31 -0
- package/dist/ui/typeAhead.js +87 -0
- package/extension/README.md +74 -0
- package/extension/background.js +575 -0
- package/extension/content.js +446 -0
- package/extension/manifest.json +36 -0
- package/extension/popup.html +22 -0
- package/extension/popup.js +12 -0
- package/package.json +66 -0
- package/skills/complex-refactor/SKILL.md +13 -0
- package/skills/deep-debugger/SKILL.md +14 -0
- 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;
|
package/dist/ui/modes.js
ADDED
|
@@ -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[];
|