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,86 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import OpenAI from "openai";
5
+ import { getEngineBaseUrl } from "./constants.js";
6
+ import { getStoredLicense } from "./license.js";
7
+ import { isManagedMode } from "./quota.js";
8
+ let versionCache = null;
9
+ /** Version del paquete instalado, para versionar el contrato con el proxy. */
10
+ export function clientVersion() {
11
+ if (versionCache)
12
+ return versionCache;
13
+ try {
14
+ const aqui = path.dirname(fileURLToPath(import.meta.url));
15
+ const pkg = JSON.parse(fs.readFileSync(path.resolve(aqui, "..", "..", "package.json"), "utf-8"));
16
+ versionCache = String(pkg.version || "0.0.0");
17
+ }
18
+ catch {
19
+ versionCache = "0.0.0";
20
+ }
21
+ return versionCache;
22
+ }
23
+ /**
24
+ * La credencial que se manda en el Bearer.
25
+ *
26
+ * En suscripcion es la clave de licencia: el cliente no tiene clave de DeepSeek
27
+ * y no debe tenerla, porque la maestra vive en el proxy. Con clave propia se
28
+ * manda la del usuario, como siempre.
29
+ */
30
+ export function engineCredential(fallbackApiKey) {
31
+ if (isManagedMode()) {
32
+ const license = getStoredLicense();
33
+ if (license?.key)
34
+ return license.key;
35
+ }
36
+ return fallbackApiKey;
37
+ }
38
+ /**
39
+ * Cabeceras propias en cada peticion.
40
+ *
41
+ * Van en TODAS las rutas para que el proxy pueda atribuir el gasto, rechazar
42
+ * clientes de version incompatible y distinguir a Chocolatito Code de un SDK
43
+ * cualquiera apuntando al mismo endpoint.
44
+ */
45
+ export function engineHeaders(route) {
46
+ const cabeceras = {
47
+ "X-Chocolatito-Client": `chocolatito-code/${clientVersion()}`,
48
+ "X-Chocolatito-Route": route,
49
+ };
50
+ const license = getStoredLicense();
51
+ // Identifica la ACTIVACION, no al cliente: permite al proxy cortar un equipo
52
+ // concreto sin tocar la licencia entera.
53
+ if (license?.instanceId)
54
+ cabeceras["X-Chocolatito-Instance"] = license.instanceId;
55
+ return cabeceras;
56
+ }
57
+ /** Cliente del motor ya configurado. Es el unico sitio donde se construye. */
58
+ export function createEngineClient(apiKey, route) {
59
+ return new OpenAI({
60
+ apiKey: engineCredential(apiKey),
61
+ baseURL: getEngineBaseUrl(),
62
+ defaultHeaders: engineHeaders(route),
63
+ });
64
+ }
65
+ /**
66
+ * Traduce un error del motor a algo que el usuario entienda.
67
+ *
68
+ * Sin esto, el proxy diciendo "se te acabo la cuota" (402) llega a pantalla como
69
+ * "Error en el motor tras varios reintentos: 402 ...", que ademas miente: un 402
70
+ * no se reintenta nunca.
71
+ */
72
+ export function explicarErrorDelMotor(err) {
73
+ const status = err?.status ?? err?.response?.status;
74
+ const mensaje = String(err?.error?.message || err?.message || "");
75
+ if (status === 402) {
76
+ return mensaje || "Has agotado la cuota de uso incluida en tu suscripciĂłn.";
77
+ }
78
+ if (status === 401 || status === 403) {
79
+ if (isManagedMode()) {
80
+ return ("Tu licencia no es válida para el motor. Comprueba tu suscripción con /licencia, " +
81
+ "o vuelve a activarla si la renovaste hace poco.");
82
+ }
83
+ return "La clave de API no es válida. Revísala en ~/.chocolatitorc.";
84
+ }
85
+ return null;
86
+ }
@@ -0,0 +1,13 @@
1
+ export declare function getGlobalConfigPath(): string;
2
+ export declare function loadSavedApiKey(): string | null;
3
+ /**
4
+ * Guarda la clave SIN borrar el resto del archivo.
5
+ *
6
+ * Antes escribia el fichero entero con solo {apiKey, updatedAt}, asi que
7
+ * guardar una clave se llevaba por delante la licencia y el modo gestionado.
8
+ * Para un suscriptor eso es fatal y silencioso: introduce una clave, pierde su
9
+ * licencia activada y el CLI deja de apuntar al proxy sin decir nada.
10
+ */
11
+ export declare function saveApiKey(apiKey: string): void;
12
+ export declare function promptForApiKey(): Promise<string>;
13
+ export declare function ensureApiKey(): Promise<string>;
@@ -0,0 +1,103 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import os from "node:os";
4
+ import dotenv from "dotenv";
5
+ import readline from "node:readline";
6
+ import chalk from "chalk";
7
+ import { CONFIG_FILE_NAME } from "./constants.js";
8
+ dotenv.config();
9
+ export function getGlobalConfigPath() {
10
+ return path.join(os.homedir(), CONFIG_FILE_NAME);
11
+ }
12
+ export function loadSavedApiKey() {
13
+ // 1. Environment variables
14
+ if (process.env.CHOCOLATITO_API_KEY?.trim())
15
+ return process.env.CHOCOLATITO_API_KEY.trim();
16
+ if (process.env.DEEPSEEK_API_KEY?.trim())
17
+ return process.env.DEEPSEEK_API_KEY.trim();
18
+ if (process.env.OPENAI_API_KEY?.trim())
19
+ return process.env.OPENAI_API_KEY.trim();
20
+ // 2. Local project .env
21
+ const localEnv = path.join(process.cwd(), ".env");
22
+ if (fs.existsSync(localEnv)) {
23
+ try {
24
+ const parsed = dotenv.parse(fs.readFileSync(localEnv, "utf-8"));
25
+ if (parsed.CHOCOLATITO_API_KEY?.trim())
26
+ return parsed.CHOCOLATITO_API_KEY.trim();
27
+ if (parsed.DEEPSEEK_API_KEY?.trim())
28
+ return parsed.DEEPSEEK_API_KEY.trim();
29
+ }
30
+ catch { }
31
+ }
32
+ // 3. Global config ~/.chocolatitorc
33
+ const globalPath = getGlobalConfigPath();
34
+ if (fs.existsSync(globalPath)) {
35
+ try {
36
+ const content = JSON.parse(fs.readFileSync(globalPath, "utf-8"));
37
+ if (content.apiKey && typeof content.apiKey === "string" && content.apiKey.trim().length > 5) {
38
+ return content.apiKey.trim();
39
+ }
40
+ }
41
+ catch { }
42
+ }
43
+ return null;
44
+ }
45
+ /**
46
+ * Guarda la clave SIN borrar el resto del archivo.
47
+ *
48
+ * Antes escribia el fichero entero con solo {apiKey, updatedAt}, asi que
49
+ * guardar una clave se llevaba por delante la licencia y el modo gestionado.
50
+ * Para un suscriptor eso es fatal y silencioso: introduce una clave, pierde su
51
+ * licencia activada y el CLI deja de apuntar al proxy sin decir nada.
52
+ */
53
+ export function saveApiKey(apiKey) {
54
+ const globalPath = getGlobalConfigPath();
55
+ let actual = {};
56
+ try {
57
+ if (fs.existsSync(globalPath))
58
+ actual = JSON.parse(fs.readFileSync(globalPath, "utf-8"));
59
+ }
60
+ catch {
61
+ // Un archivo corrupto se reemplaza; no hay nada que conservar.
62
+ }
63
+ const config = {
64
+ ...actual,
65
+ apiKey: apiKey.trim(),
66
+ updatedAt: new Date().toISOString(),
67
+ };
68
+ fs.writeFileSync(globalPath, JSON.stringify(config, null, 2), "utf-8");
69
+ }
70
+ export async function promptForApiKey() {
71
+ if (!process.stdin.isTTY) {
72
+ console.error(chalk.red("\nError: No se encontrĂł ninguna API Key configurada y no hay terminal interactiva.\n" +
73
+ "Configura la variable de entorno CHOCOLATITO_API_KEY o DEEPSEEK_API_KEY en tu entorno.\n"));
74
+ process.exit(1);
75
+ }
76
+ return new Promise((resolve) => {
77
+ const rl = readline.createInterface({
78
+ input: process.stdin,
79
+ output: process.stdout,
80
+ });
81
+ console.log(chalk.bold.hex("#D97757")("\n🔑 CONFIGURACIÓN DE ACCESO — CHOCOLATITO CODE"));
82
+ console.log(chalk.gray("Ingresa tu API Key (se guardará de forma segura en ~/.chocolatitorc):"));
83
+ rl.question(chalk.hex("#A855F7")("API Key ❯ "), (key) => {
84
+ rl.close();
85
+ const cleaned = key.trim();
86
+ if (cleaned.length > 5) {
87
+ saveApiKey(cleaned);
88
+ console.log(chalk.green("âś” API Key guardada exitosamente.\n"));
89
+ resolve(cleaned);
90
+ }
91
+ else {
92
+ console.log(chalk.red("Error: Clave no válida."));
93
+ process.exit(1);
94
+ }
95
+ });
96
+ });
97
+ }
98
+ export async function ensureApiKey() {
99
+ const existing = loadSavedApiKey();
100
+ if (existing)
101
+ return existing;
102
+ return await promptForApiKey();
103
+ }
@@ -0,0 +1,39 @@
1
+ export interface StoredLicense {
2
+ key: string;
3
+ instanceId: string;
4
+ /** ISO de la ultima confirmacion buena. */
5
+ lastValidated: string;
6
+ customerEmail?: string;
7
+ productName?: string;
8
+ }
9
+ export declare function getStoredLicense(): StoredLicense | null;
10
+ export type LicenseOutcome = {
11
+ kind: "ok";
12
+ license: StoredLicense;
13
+ }
14
+ /** Lemon Squeezy respondio que no. Reembolso, cancelacion, tope de maquinas. */
15
+ | {
16
+ kind: "rejected";
17
+ reason: string;
18
+ }
19
+ /** No se pudo preguntar: red, caida del servicio, tiempo agotado. */
20
+ | {
21
+ kind: "unreachable";
22
+ reason: string;
23
+ };
24
+ export declare function activate(licenseKey: string): Promise<LicenseOutcome>;
25
+ export declare function validate(license: StoredLicense): Promise<LicenseOutcome>;
26
+ /** Libera esta maquina para poder usar la clave en otra. */
27
+ export declare function deactivate(): Promise<{
28
+ ok: boolean;
29
+ message: string;
30
+ }>;
31
+ /**
32
+ * Puerta de entrada. Devuelve true si se puede seguir.
33
+ *
34
+ * Se salta entera en desarrollo con CHOCOLATITO_LICENSE_SKIP=1. No debilita
35
+ * nada: quien pueda poner esa variable tambien puede editar el dist/.
36
+ */
37
+ export declare function ensureLicense(): Promise<boolean>;
38
+ /** Texto del comando /licencia. */
39
+ export declare function licenseStatus(): string;
@@ -0,0 +1,306 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import readline from "node:readline";
5
+ import chalk from "chalk";
6
+ import { getGlobalConfigPath } from "./env.js";
7
+ /**
8
+ * LICENCIAS
9
+ *
10
+ * Contra la API de licencias de Lemon Squeezy, que tiene una propiedad muy util:
11
+ * las llamadas se autentican con la clave del propio cliente, sin cabecera de
12
+ * autorizacion. No hace falta servidor, ni base de datos, ni meter ninguna clave
13
+ * maestra dentro del programa que se reparte.
14
+ *
15
+ * DOS FALLOS QUE HAY QUE EVITAR Y QUE NO SON OBVIOS:
16
+ *
17
+ * 1. Validar sin comprobar la TIENDA. La API responde a cualquier clave de
18
+ * cualquier tienda de Lemon Squeezy. Sin comprobar el store_id, alguien
19
+ * compra un producto de un euro en otra tienda cualquiera y su clave abre
20
+ * Chocolatito Code.
21
+ *
22
+ * 2. Confundir "me han dicho que no" con "no he podido preguntar". Si un fallo
23
+ * de red se trata como licencia invalida, una caida de una hora de Lemon
24
+ * Squeezy deja tirados a todos los clientes a la vez. Una respuesta negativa
25
+ * corta al instante; no poder preguntar da margen.
26
+ *
27
+ * Y lo que esto NO es: corre en la maquina del cliente, en JavaScript legible.
28
+ * Frena que se comparta la clave con un amigo, no a quien quiera piratearlo.
29
+ * La barrera de verdad solo llega cuando el motor lo pagas tu y vive detras de
30
+ * tu servidor.
31
+ */
32
+ const API = "https://api.lemonsqueezy.com/v1/licenses";
33
+ /** Tienda propietaria del producto. Una clave de otra tienda no vale aqui. */
34
+ const STORE_ID = Number(process.env.CHOCOLATITO_STORE_ID || 463680);
35
+ /** Dias que se aguanta sin poder confirmar antes de exigir conexion. */
36
+ const GRACIA_DIAS = Number(process.env.CHOCOLATITO_LICENSE_GRACE_DAYS || 7);
37
+ /** Horas entre comprobaciones. Por debajo de esto ni se llama. */
38
+ const REVALIDAR_HORAS = Number(process.env.CHOCOLATITO_LICENSE_RECHECK_HOURS || 24);
39
+ function readRc() {
40
+ try {
41
+ const p = getGlobalConfigPath();
42
+ if (!fs.existsSync(p))
43
+ return {};
44
+ return JSON.parse(fs.readFileSync(p, "utf-8"));
45
+ }
46
+ catch {
47
+ return {};
48
+ }
49
+ }
50
+ function writeRc(patch) {
51
+ try {
52
+ const p = getGlobalConfigPath();
53
+ const actual = readRc();
54
+ fs.writeFileSync(p, JSON.stringify({ ...actual, ...patch }, null, 2), "utf-8");
55
+ }
56
+ catch { }
57
+ }
58
+ export function getStoredLicense() {
59
+ return readRc().license || null;
60
+ }
61
+ /** Nombre para reconocer esta maquina en el panel de Lemon Squeezy. */
62
+ function nombreDeMaquina() {
63
+ const host = os.hostname() || "equipo";
64
+ return `${host} (${os.platform()})`;
65
+ }
66
+ async function post(endpoint, params) {
67
+ const res = await fetch(`${API}/${endpoint}`, {
68
+ method: "POST",
69
+ headers: {
70
+ Accept: "application/json",
71
+ "Content-Type": "application/x-www-form-urlencoded",
72
+ },
73
+ body: new URLSearchParams(params).toString(),
74
+ signal: AbortSignal.timeout(15_000),
75
+ });
76
+ // 400 y 404 traen cuerpo JSON con el motivo; no son fallos de red.
77
+ return await res.json();
78
+ }
79
+ /** La clave tiene que ser de NUESTRA tienda, no de una cualquiera. */
80
+ function esDeNuestraTienda(data) {
81
+ const store = data?.meta?.store_id;
82
+ return typeof store === "number" && store === STORE_ID;
83
+ }
84
+ export async function activate(licenseKey) {
85
+ let data;
86
+ try {
87
+ data = await post("activate", {
88
+ license_key: licenseKey.trim(),
89
+ instance_name: nombreDeMaquina(),
90
+ });
91
+ }
92
+ catch (err) {
93
+ return { kind: "unreachable", reason: err?.message || String(err) };
94
+ }
95
+ if (!data?.activated) {
96
+ return { kind: "rejected", reason: traducir(data?.error) };
97
+ }
98
+ if (!esDeNuestraTienda(data)) {
99
+ return {
100
+ kind: "rejected",
101
+ reason: "Esa clave es de otra tienda, no de Chocolatito Code.",
102
+ };
103
+ }
104
+ const license = {
105
+ key: licenseKey.trim(),
106
+ instanceId: data.instance.id,
107
+ lastValidated: new Date().toISOString(),
108
+ customerEmail: data?.meta?.customer_email,
109
+ productName: data?.meta?.product_name,
110
+ };
111
+ // Activar una licencia ES entrar en modo suscripcion: el motor lo paga
112
+ // Chocolatito y el cliente no necesita clave de DeepSeek. Sin escribir esto,
113
+ // el CLI seguiria apuntando a api.deepseek.com y pidiendole al suscriptor una
114
+ // clave que no tiene por que tener.
115
+ const yaConfigurado = readRc();
116
+ const parche = { license };
117
+ if (yaConfigurado.managed !== false)
118
+ parche.managed = true;
119
+ writeRc(parche);
120
+ return { kind: "ok", license };
121
+ }
122
+ export async function validate(license) {
123
+ let data;
124
+ try {
125
+ data = await post("validate", {
126
+ license_key: license.key,
127
+ instance_id: license.instanceId,
128
+ });
129
+ }
130
+ catch (err) {
131
+ return { kind: "unreachable", reason: err?.message || String(err) };
132
+ }
133
+ if (!data?.valid) {
134
+ return { kind: "rejected", reason: traducir(data?.error) };
135
+ }
136
+ if (!esDeNuestraTienda(data)) {
137
+ return { kind: "rejected", reason: "Esa clave es de otra tienda, no de Chocolatito Code." };
138
+ }
139
+ const actualizada = {
140
+ ...license,
141
+ lastValidated: new Date().toISOString(),
142
+ customerEmail: data?.meta?.customer_email || license.customerEmail,
143
+ productName: data?.meta?.product_name || license.productName,
144
+ };
145
+ writeRc({ license: actualizada });
146
+ return { kind: "ok", license: actualizada };
147
+ }
148
+ /** Libera esta maquina para poder usar la clave en otra. */
149
+ export async function deactivate() {
150
+ const license = getStoredLicense();
151
+ if (!license)
152
+ return { ok: false, message: "No hay ninguna licencia guardada en este equipo." };
153
+ try {
154
+ const data = await post("deactivate", {
155
+ license_key: license.key,
156
+ instance_id: license.instanceId,
157
+ });
158
+ if (!data?.deactivated) {
159
+ return { ok: false, message: traducir(data?.error) };
160
+ }
161
+ writeRc({ license: undefined, managed: false });
162
+ return {
163
+ ok: true,
164
+ message: "Licencia liberada en este equipo. Ya puedes activarla en otro.",
165
+ };
166
+ }
167
+ catch (err) {
168
+ return {
169
+ ok: false,
170
+ message: `No se pudo contactar con Lemon Squeezy: ${err?.message || String(err)}`,
171
+ };
172
+ }
173
+ }
174
+ const MENSAJES = [
175
+ [/activation limit/i, "Esta clave ya se esta usando en el maximo de equipos permitido. Libera uno con /licencia liberar."],
176
+ [/license key has been disabled|disabled/i, "Esta licencia esta desactivada. Si crees que es un error, escribe al soporte."],
177
+ [/expired/i, "Esta licencia ha caducado."],
178
+ [/not found|invalid/i, "Esa clave no existe. Revisa que la hayas copiado entera."],
179
+ [/instance/i, "Esta maquina ya no figura como activada. Vuelve a introducir tu clave."],
180
+ ];
181
+ function traducir(error) {
182
+ const texto = String(error || "La licencia no es valida.");
183
+ for (const [patron, mensaje] of MENSAJES) {
184
+ if (patron.test(texto))
185
+ return mensaje;
186
+ }
187
+ return texto;
188
+ }
189
+ /**
190
+ * True cuando se ejecuta desde el codigo fuente, no desde el paquete instalado.
191
+ *
192
+ * Sin esto, el propio autor se queda fuera de su herramienta en cuanto se activa
193
+ * la licencia: trabaja sobre el repo y no tiene clave que meter.
194
+ *
195
+ * Se distingue por el tsconfig.json, que existe en el repo y NO se publica (el
196
+ * campo "files" del package.json solo incluye dist, extension y los textos). Un
197
+ * cliente nunca lo tiene, asi que esto no abre ninguna puerta en el producto.
198
+ */
199
+ function enDesarrollo() {
200
+ try {
201
+ const aqui = path.dirname(new URL(import.meta.url).pathname.replace(/^\/([A-Za-z]:)/, "$1"));
202
+ const raiz = path.resolve(aqui, "..", "..");
203
+ return fs.existsSync(path.join(raiz, "tsconfig.json")) && fs.existsSync(path.join(raiz, "src"));
204
+ }
205
+ catch {
206
+ return false;
207
+ }
208
+ }
209
+ function horasDesde(iso) {
210
+ const t = Date.parse(iso);
211
+ if (!Number.isFinite(t))
212
+ return Infinity;
213
+ return (Date.now() - t) / 3_600_000;
214
+ }
215
+ async function pedirClave() {
216
+ return new Promise((resolve) => {
217
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
218
+ console.log(chalk.bold.hex("#D97757")("\n🔑 ACTIVACIÓN — CHOCOLATITO CODE"));
219
+ console.log(chalk.gray("Pega la clave de licencia que recibiste por correo al comprar."));
220
+ rl.question(chalk.hex("#A855F7")("Licencia ❯ "), (respuesta) => {
221
+ rl.close();
222
+ resolve(respuesta.trim());
223
+ });
224
+ });
225
+ }
226
+ /**
227
+ * Puerta de entrada. Devuelve true si se puede seguir.
228
+ *
229
+ * Se salta entera en desarrollo con CHOCOLATITO_LICENSE_SKIP=1. No debilita
230
+ * nada: quien pueda poner esa variable tambien puede editar el dist/.
231
+ */
232
+ export async function ensureLicense() {
233
+ if (process.env.CHOCOLATITO_LICENSE_SKIP === "1")
234
+ return true;
235
+ if (enDesarrollo())
236
+ return true;
237
+ let license = getStoredLicense();
238
+ // Primera vez: se pide la clave y se activa esta maquina.
239
+ if (!license) {
240
+ if (!process.stdin.isTTY) {
241
+ console.log(chalk.red("\nâś– No hay licencia activada en este equipo") +
242
+ chalk.gray("\n Ejecuta 'chocolatito' en una terminal para introducirla.\n"));
243
+ return false;
244
+ }
245
+ const clave = await pedirClave();
246
+ if (!clave) {
247
+ console.log(chalk.red("\nâś– No se introdujo ninguna clave.\n"));
248
+ return false;
249
+ }
250
+ const res = await activate(clave);
251
+ if (res.kind === "ok") {
252
+ console.log(chalk.green(`\nâś” Licencia activada${res.license.customerEmail ? ` para ${res.license.customerEmail}` : ""}.\n`));
253
+ return true;
254
+ }
255
+ if (res.kind === "rejected") {
256
+ console.log(chalk.red(`\nâś– ${res.reason}\n`));
257
+ return false;
258
+ }
259
+ console.log(chalk.red(`\nâś– No se pudo contactar con Lemon Squeezy para activar (${res.reason}).`) +
260
+ chalk.gray("\n Comprueba tu conexión e inténtalo otra vez.\n"));
261
+ return false;
262
+ }
263
+ // Ya activada: solo se vuelve a preguntar de vez en cuando.
264
+ if (horasDesde(license.lastValidated) < REVALIDAR_HORAS)
265
+ return true;
266
+ const res = await validate(license);
267
+ if (res.kind === "ok")
268
+ return true;
269
+ if (res.kind === "rejected") {
270
+ // Respuesta clara de Lemon Squeezy: se corta ya.
271
+ console.log(chalk.red(`\nâś– ${res.reason}\n`));
272
+ return false;
273
+ }
274
+ // No se pudo preguntar. Aqui es donde importa no castigar al cliente por una
275
+ // caida que no es suya.
276
+ const dias = horasDesde(license.lastValidated) / 24;
277
+ if (dias <= GRACIA_DIAS) {
278
+ const quedan = Math.max(0, Math.ceil(GRACIA_DIAS - dias));
279
+ console.log(chalk.yellow(`âš  No se pudo comprobar la licencia (${res.reason}).`) +
280
+ chalk.gray(` Sigues trabajando; quedan ${quedan} dĂ­a(s) de margen.\n`));
281
+ return true;
282
+ }
283
+ console.log(chalk.red(`\n✖ Llevas más de ${GRACIA_DIAS} días sin poder comprobar la licencia.`) +
284
+ chalk.gray("\n Conéctate a internet una vez para reanudarla.\n"));
285
+ return false;
286
+ }
287
+ /** Texto del comando /licencia. */
288
+ export function licenseStatus() {
289
+ const l = getStoredLicense();
290
+ if (!l)
291
+ return chalk.yellow("No hay ninguna licencia activada en este equipo.");
292
+ const horas = horasDesde(l.lastValidated);
293
+ const cuando = horas < 1 ? "hace menos de una hora" : `hace ${Math.floor(horas)} h`;
294
+ return [
295
+ chalk.bold.hex("#D97757")("Licencia de Chocolatito Code"),
296
+ ` Producto: ${l.productName || "Chocolatito Code"}`,
297
+ l.customerEmail ? ` A nombre de: ${l.customerEmail}` : "",
298
+ ` Clave: ${l.key.slice(0, 8)}…${l.key.slice(-4)}`,
299
+ ` Equipo: ${nombreDeMaquina()}`,
300
+ ` Comprobada: ${cuando}`,
301
+ "",
302
+ chalk.gray(" /licencia liberar — suelta este equipo para usar la clave en otro"),
303
+ ]
304
+ .filter(Boolean)
305
+ .join("\n");
306
+ }
@@ -0,0 +1,38 @@
1
+ export type PermissionMode = "default" | "acceptEdits" | "plan" | "bypass";
2
+ export interface SettingsConfig {
3
+ permissions?: {
4
+ mode?: PermissionMode;
5
+ allowPatterns?: string[];
6
+ denyPatterns?: string[];
7
+ };
8
+ }
9
+ /**
10
+ * Detecta comandos destructivos o de alto riesgo que siempre deben pedir confirmaciĂłn.
11
+ * Comandos seguros de desarrollo (python, npm, pytest, tsc, git status, etc.) no interrumpen al usuario.
12
+ */
13
+ export declare function isDangerousCommand(command: string): boolean;
14
+ export declare class PermissionManager {
15
+ mode: PermissionMode;
16
+ allowPatterns: Set<string>;
17
+ denyPatterns: Set<string>;
18
+ private settingsFilePath;
19
+ constructor(initialMode?: PermissionMode, cwd?: string);
20
+ loadSettings(): void;
21
+ private applySettings;
22
+ saveAllowPattern(pattern: string): void;
23
+ setMode(mode: PermissionMode): void;
24
+ getMode(): PermissionMode;
25
+ isReadOnly(): boolean;
26
+ /**
27
+ * Modo plan = SOLO LECTURA de verdad.
28
+ */
29
+ isBlockedInPlanMode(toolName: string, args: Record<string, any>): string | null;
30
+ requiresConfirmation(toolName: string, args: Record<string, any>): boolean;
31
+ /**
32
+ * Un patron autorizado vale para ESE comando, no para cualquiera que lo
33
+ * contenga. Con la comparacion por subcadena de antes, autorizar "git status"
34
+ * dejaba pasar tambien "rm -rf . && git status": bastaba con que el patron
35
+ * apareciera en algun sitio de la linea.
36
+ */
37
+ private isPatternAllowed;
38
+ }