@qubiit/lmagent 3.1.4 → 3.1.8
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/.agents/rules/00-master.md +2 -2
- package/.agents/skills/ai-agent-engineer/SKILL.md +1 -1
- package/.agents/skills/api-designer/SKILL.md +1 -1
- package/.agents/skills/architect/SKILL.md +1 -1
- package/.agents/skills/automation-engineer/SKILL.md +1 -1
- package/.agents/skills/backend-engineer/SKILL.md +1 -1
- package/.agents/skills/bmad-methodology/SKILL.md +1 -1
- package/.agents/skills/browser-agent/SKILL.md +1 -1
- package/.agents/skills/code-reviewer/SKILL.md +1 -1
- package/.agents/skills/data-engineer/SKILL.md +1 -1
- package/.agents/skills/devops-engineer/SKILL.md +1 -1
- package/.agents/skills/document-generator/SKILL.md +1 -1
- package/.agents/skills/frontend-engineer/SKILL.md +1 -1
- package/.agents/skills/git-workflow/SKILL.md +1 -1
- package/.agents/skills/mcp-builder/SKILL.md +1 -1
- package/.agents/skills/mobile-engineer/SKILL.md +1 -1
- package/.agents/skills/orchestrator/SKILL.md +1 -1
- package/.agents/skills/performance-engineer/SKILL.md +1 -1
- package/.agents/skills/product-manager/SKILL.md +1 -1
- package/.agents/skills/prompt-engineer/SKILL.md +1 -1
- package/.agents/skills/qa-engineer/SKILL.md +1 -1
- package/.agents/skills/scrum-master/SKILL.md +1 -1
- package/.agents/skills/security-analyst/SKILL.md +1 -1
- package/.agents/skills/seo-auditor/SKILL.md +1 -1
- package/.agents/skills/spec-driven-dev/SKILL.md +1 -1
- package/.agents/skills/supabase-expert/SKILL.md +1 -1
- package/.agents/skills/swe-agent/SKILL.md +1 -1
- package/.agents/skills/systematic-debugger/SKILL.md +1 -1
- package/.agents/skills/tech-lead/SKILL.md +1 -1
- package/.agents/skills/technical-writer/SKILL.md +1 -1
- package/.agents/skills/testing-strategist/SKILL.md +1 -1
- package/.agents/skills/ux-ui-designer/SKILL.md +1 -1
- package/install.js +43 -24
- package/package.json +3 -2
- package/scripts/create_skill.js +300 -0
- package/scripts/fix_descriptions.js +51 -0
- package/scripts/token-analyzer.js +275 -0
- package/scripts/validate_skills.js +285 -0
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* LMAgent Skill Generator — v3.0.13
|
|
5
|
+
*
|
|
6
|
+
* Genera la estructura completa de un nuevo skill interactivamente.
|
|
7
|
+
*
|
|
8
|
+
* Uso:
|
|
9
|
+
* node scripts/create_skill.js # Interactivo
|
|
10
|
+
* node scripts/create_skill.js --name "My Skill" # Semi-automático
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { mkdirSync, writeFileSync, existsSync } from 'fs';
|
|
14
|
+
import { join, resolve, dirname } from 'path';
|
|
15
|
+
import { fileURLToPath } from 'url';
|
|
16
|
+
import { createInterface } from 'readline';
|
|
17
|
+
|
|
18
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
19
|
+
const __dirname = dirname(__filename);
|
|
20
|
+
const ROOT = resolve(__dirname, '..');
|
|
21
|
+
const SKILLS_DIR = join(ROOT, 'skills');
|
|
22
|
+
|
|
23
|
+
// ─── Colores ──────────────────────────────────────────────────
|
|
24
|
+
const c = {
|
|
25
|
+
red: (s) => `\x1b[31m${s}\x1b[0m`,
|
|
26
|
+
green: (s) => `\x1b[32m${s}\x1b[0m`,
|
|
27
|
+
yellow: (s) => `\x1b[33m${s}\x1b[0m`,
|
|
28
|
+
cyan: (s) => `\x1b[36m${s}\x1b[0m`,
|
|
29
|
+
bold: (s) => `\x1b[1m${s}\x1b[0m`,
|
|
30
|
+
dim: (s) => `\x1b[2m${s}\x1b[0m`,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// ─── Prompt interactivo ───────────────────────────────────────
|
|
34
|
+
function createPrompt() {
|
|
35
|
+
const rl = createInterface({
|
|
36
|
+
input: process.stdin,
|
|
37
|
+
output: process.stdout,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
ask: (question, defaultValue = '') => {
|
|
42
|
+
return new Promise((resolve) => {
|
|
43
|
+
const suffix = defaultValue ? ` ${c.dim(`(${defaultValue})`)}` : '';
|
|
44
|
+
rl.question(` ${c.cyan('?')} ${question}${suffix}: `, (answer) => {
|
|
45
|
+
resolve(answer.trim() || defaultValue);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
},
|
|
49
|
+
askList: (question, hint = 'separar con comas') => {
|
|
50
|
+
return new Promise((resolve) => {
|
|
51
|
+
rl.question(` ${c.cyan('?')} ${question} ${c.dim(`(${hint})`)}: `, (answer) => {
|
|
52
|
+
const items = answer.split(',').map(s => s.trim()).filter(Boolean);
|
|
53
|
+
resolve(items);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
},
|
|
57
|
+
askYesNo: (question, defaultValue = true) => {
|
|
58
|
+
return new Promise((resolve) => {
|
|
59
|
+
const hint = defaultValue ? 'S/n' : 's/N';
|
|
60
|
+
rl.question(` ${c.cyan('?')} ${question} ${c.dim(`(${hint})`)}: `, (answer) => {
|
|
61
|
+
if (!answer.trim()) return resolve(defaultValue);
|
|
62
|
+
resolve(['s', 'si', 'sí', 'y', 'yes'].includes(answer.trim().toLowerCase()));
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
},
|
|
66
|
+
close: () => rl.close(),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ─── Generar slug del nombre ──────────────────────────────────
|
|
71
|
+
function slugify(name) {
|
|
72
|
+
return name
|
|
73
|
+
.toLowerCase()
|
|
74
|
+
.replace(/[áàäâ]/g, 'a')
|
|
75
|
+
.replace(/[éèëê]/g, 'e')
|
|
76
|
+
.replace(/[íìïî]/g, 'i')
|
|
77
|
+
.replace(/[óòöô]/g, 'o')
|
|
78
|
+
.replace(/[úùüû]/g, 'u')
|
|
79
|
+
.replace(/ñ/g, 'n')
|
|
80
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
81
|
+
.replace(/^-|-$/g, '');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ─── Generar SKILL.md ─────────────────────────────────────────
|
|
85
|
+
function generateSkillMd(data) {
|
|
86
|
+
const expertise = data.expertise.map(e => ` - ${e}`).join('\n');
|
|
87
|
+
const activatesOn = data.activatesOn.map(a => ` - ${a}`).join('\n');
|
|
88
|
+
const triggers = data.triggers.map(t => ` - ${t}`).join('\n');
|
|
89
|
+
|
|
90
|
+
return `---
|
|
91
|
+
name: ${data.name}
|
|
92
|
+
description: ${data.description}
|
|
93
|
+
role: ${data.role}
|
|
94
|
+
type: ${data.type}
|
|
95
|
+
version: 3.0.0
|
|
96
|
+
icon: ${data.icon}
|
|
97
|
+
expertise:
|
|
98
|
+
${expertise}
|
|
99
|
+
activates_on:
|
|
100
|
+
${activatesOn}
|
|
101
|
+
triggers:
|
|
102
|
+
${triggers}
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
# ${data.name} Persona
|
|
106
|
+
|
|
107
|
+
## 🧠 System Prompt
|
|
108
|
+
> **Instrucciones para el LLM**: Copia este bloque en tu system prompt o contexto inicial.
|
|
109
|
+
|
|
110
|
+
\`\`\`markdown
|
|
111
|
+
Eres **${data.name}**, un experto en ${data.role.toLowerCase()}.
|
|
112
|
+
Tu objetivo es **[DEFINIR OBJETIVO PRINCIPAL EN MAYÚSCULAS]**.
|
|
113
|
+
Tu tono es **[Adjetivo 1, Adjetivo 2, Adjetivo 3]**.
|
|
114
|
+
|
|
115
|
+
**Principios Core:**
|
|
116
|
+
1. **[Principio 1]**: [Descripción]
|
|
117
|
+
2. **[Principio 2]**: [Descripción]
|
|
118
|
+
3. **[Principio 3]**: [Descripción]
|
|
119
|
+
4. **[Principio 4]**: [Descripción]
|
|
120
|
+
|
|
121
|
+
**Restricciones:**
|
|
122
|
+
- NUNCA [restricción 1].
|
|
123
|
+
- SIEMPRE [restricción 2].
|
|
124
|
+
- SIEMPRE [restricción 3].
|
|
125
|
+
- NUNCA [restricción 4].
|
|
126
|
+
\`\`\`
|
|
127
|
+
|
|
128
|
+
## 🔄 Arquitectura Cognitiva (Cómo Pensar)
|
|
129
|
+
|
|
130
|
+
### 1. Fase de Análisis
|
|
131
|
+
Antes de actuar, pregúntate:
|
|
132
|
+
- **Input**: ¿Qué se necesita?
|
|
133
|
+
- **Contexto**: ¿Qué restricciones existen?
|
|
134
|
+
- **Riesgo**: ¿Qué puede salir mal?
|
|
135
|
+
- **Salida**: ¿Cuál es el resultado esperado?
|
|
136
|
+
|
|
137
|
+
### 2. Fase de Diseño
|
|
138
|
+
- Definir **estructura** del entregable.
|
|
139
|
+
- Planear **enfoque** paso a paso.
|
|
140
|
+
- Identificar **dependencias** y **bloqueantes**.
|
|
141
|
+
|
|
142
|
+
### 3. Fase de Ejecución
|
|
143
|
+
- Implementar según el plan.
|
|
144
|
+
- Verificar en cada paso.
|
|
145
|
+
- Documentar decisiones.
|
|
146
|
+
|
|
147
|
+
### 4. Auto-Corrección
|
|
148
|
+
Antes de finalizar, verifica:
|
|
149
|
+
- "¿Cumple con los criterios de aceptación?"
|
|
150
|
+
- "¿Sigue los patrones del proyecto?"
|
|
151
|
+
- "¿Es mantenible y documentado?"
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Rol
|
|
156
|
+
|
|
157
|
+
${data.description}
|
|
158
|
+
|
|
159
|
+
## Responsabilidades
|
|
160
|
+
|
|
161
|
+
1. **[Responsabilidad 1]**: [Detalle]
|
|
162
|
+
2. **[Responsabilidad 2]**: [Detalle]
|
|
163
|
+
3. **[Responsabilidad 3]**: [Detalle]
|
|
164
|
+
4. **[Responsabilidad 4]**: [Detalle]
|
|
165
|
+
5. **[Responsabilidad 5]**: [Detalle]
|
|
166
|
+
|
|
167
|
+
## Stack Técnico
|
|
168
|
+
|
|
169
|
+
\`\`\`
|
|
170
|
+
[Tecnología 1] → [Propósito]
|
|
171
|
+
[Tecnología 2] → [Propósito]
|
|
172
|
+
[Tecnología 3] → [Propósito]
|
|
173
|
+
\`\`\`
|
|
174
|
+
|
|
175
|
+
## Interacción con Otros Roles
|
|
176
|
+
|
|
177
|
+
| Rol | Colaboración |
|
|
178
|
+
|-----|-------------|
|
|
179
|
+
| [Rol 1] | [Cómo colaboran] |
|
|
180
|
+
| [Rol 2] | [Cómo colaboran] |
|
|
181
|
+
| [Rol 3] | [Cómo colaboran] |
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## 🛠️ Herramientas Preferidas
|
|
186
|
+
|
|
187
|
+
| Herramienta | Cuándo Usarla |
|
|
188
|
+
|-------------|---------------|
|
|
189
|
+
| \`view_file\` | [Cuándo] |
|
|
190
|
+
| \`run_command\` | [Cuándo] |
|
|
191
|
+
| \`grep_search\` | [Cuándo] |
|
|
192
|
+
| \`write_to_file\` | [Cuándo] |
|
|
193
|
+
|
|
194
|
+
## 📋 Definition of Done
|
|
195
|
+
|
|
196
|
+
Antes de considerar una tarea terminada, verifica TODO:
|
|
197
|
+
|
|
198
|
+
### Calidad
|
|
199
|
+
- [ ] [Criterio 1]
|
|
200
|
+
- [ ] [Criterio 2]
|
|
201
|
+
- [ ] [Criterio 3]
|
|
202
|
+
|
|
203
|
+
### Documentación
|
|
204
|
+
- [ ] [Criterio de documentación 1]
|
|
205
|
+
- [ ] [Criterio de documentación 2]
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
*Skill version: 2.7 | LMAgent Framework*
|
|
210
|
+
`;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ─── Main ─────────────────────────────────────────────────────
|
|
214
|
+
async function main() {
|
|
215
|
+
console.log(c.bold('\n🛠️ LMAgent Skill Generator v3.0.0\n'));
|
|
216
|
+
|
|
217
|
+
const prompt = createPrompt();
|
|
218
|
+
|
|
219
|
+
try {
|
|
220
|
+
// Obtener datos del skill
|
|
221
|
+
const name = await prompt.ask('Nombre del skill', '');
|
|
222
|
+
if (!name) {
|
|
223
|
+
console.log(c.red('\n❌ El nombre es obligatorio.\n'));
|
|
224
|
+
process.exit(1);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const slug = slugify(name);
|
|
228
|
+
const skillDir = join(SKILLS_DIR, slug);
|
|
229
|
+
|
|
230
|
+
if (existsSync(skillDir)) {
|
|
231
|
+
console.log(c.red(`\n❌ Ya existe un skill en: ${skillDir}\n`));
|
|
232
|
+
process.exit(1);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const description = await prompt.ask('Descripción breve', '');
|
|
236
|
+
const role = await prompt.ask('Rol del skill', `Experto en ${name}`);
|
|
237
|
+
const icon = await prompt.ask('Icono (emoji)', '🔧');
|
|
238
|
+
const type = await prompt.ask('Tipo (agent_persona / methodology)', 'agent_persona');
|
|
239
|
+
const expertise = await prompt.askList('Áreas de expertise');
|
|
240
|
+
const activatesOn = await prompt.askList('Cuándo se activa (contextos)');
|
|
241
|
+
|
|
242
|
+
const triggerSuggestion = `/${slug.split('-')[0]}`;
|
|
243
|
+
const triggersRaw = await prompt.askList('Triggers (con /)', `ej: ${triggerSuggestion}`);
|
|
244
|
+
const triggers = triggersRaw.length > 0 ? triggersRaw : [triggerSuggestion];
|
|
245
|
+
|
|
246
|
+
const createDirs = await prompt.askYesNo('¿Crear subdirectorios (scripts/, references/, assets/)?', false);
|
|
247
|
+
|
|
248
|
+
// Confirmar
|
|
249
|
+
console.log(c.bold('\n📋 Resumen:'));
|
|
250
|
+
console.log(` Nombre: ${c.cyan(name)}`);
|
|
251
|
+
console.log(` Slug: ${c.dim(slug)}`);
|
|
252
|
+
console.log(` Descripción: ${description}`);
|
|
253
|
+
console.log(` Rol: ${role}`);
|
|
254
|
+
console.log(` Icono: ${icon}`);
|
|
255
|
+
console.log(` Tipo: ${type}`);
|
|
256
|
+
console.log(` Expertise: ${expertise.join(', ')}`);
|
|
257
|
+
console.log(` Activa en: ${activatesOn.join(', ')}`);
|
|
258
|
+
console.log(` Triggers: ${triggers.join(', ')}`);
|
|
259
|
+
console.log(` Extra dirs: ${createDirs ? 'Sí' : 'No'}`);
|
|
260
|
+
|
|
261
|
+
const confirm = await prompt.askYesNo('\n¿Crear skill?', true);
|
|
262
|
+
if (!confirm) {
|
|
263
|
+
console.log(c.yellow('\n⚠️ Cancelado.\n'));
|
|
264
|
+
process.exit(0);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Crear estructura
|
|
268
|
+
mkdirSync(skillDir, { recursive: true });
|
|
269
|
+
|
|
270
|
+
if (createDirs) {
|
|
271
|
+
mkdirSync(join(skillDir, 'scripts'), { recursive: true });
|
|
272
|
+
mkdirSync(join(skillDir, 'references'), { recursive: true });
|
|
273
|
+
mkdirSync(join(skillDir, 'assets'), { recursive: true });
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Generar SKILL.md
|
|
277
|
+
const skillMd = generateSkillMd({
|
|
278
|
+
name,
|
|
279
|
+
description,
|
|
280
|
+
role,
|
|
281
|
+
icon,
|
|
282
|
+
type,
|
|
283
|
+
expertise,
|
|
284
|
+
activatesOn,
|
|
285
|
+
triggers,
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
writeFileSync(join(skillDir, 'SKILL.md'), skillMd, 'utf-8');
|
|
289
|
+
|
|
290
|
+
console.log(c.green(`\n✅ Skill creado exitosamente en: ${skillDir}`));
|
|
291
|
+
console.log(c.dim(' Editá SKILL.md para completar las secciones con placeholders [...]'));
|
|
292
|
+
console.log(c.dim(' Ejecutá: node scripts/validate_skills.js ' + slug + ' para validar'));
|
|
293
|
+
console.log('');
|
|
294
|
+
|
|
295
|
+
} finally {
|
|
296
|
+
prompt.close();
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
main().catch(console.error);
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
const skillsDir = path.join(__dirname, '..', '.agents', 'skills');
|
|
5
|
+
|
|
6
|
+
function fixDescriptions() {
|
|
7
|
+
if (!fs.existsSync(skillsDir)) {
|
|
8
|
+
console.error('Skills directory not found:', skillsDir);
|
|
9
|
+
process.exit(1);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const skills = fs.readdirSync(skillsDir);
|
|
13
|
+
let updated = 0;
|
|
14
|
+
|
|
15
|
+
skills.forEach(skill => {
|
|
16
|
+
const skillPath = path.join(skillsDir, skill, 'SKILL.md');
|
|
17
|
+
if (fs.existsSync(skillPath)) {
|
|
18
|
+
let content = fs.readFileSync(skillPath, 'utf8');
|
|
19
|
+
|
|
20
|
+
// Regex to find description line
|
|
21
|
+
// Captures "description: value" where value is NOT double-quoted
|
|
22
|
+
// Note: This is a simple heuristic. It assumes description is on one line.
|
|
23
|
+
const regex = /^description: ([^"].*)$/m;
|
|
24
|
+
|
|
25
|
+
const match = content.match(regex);
|
|
26
|
+
|
|
27
|
+
if (match) {
|
|
28
|
+
const originalDescription = match[1].trim();
|
|
29
|
+
// Escape existing quotes if any (though regex excludes starting quote)
|
|
30
|
+
const escapedDescription = originalDescription.replace(/"/g, '\\"');
|
|
31
|
+
const newLine = `description: "${escapedDescription}"`;
|
|
32
|
+
|
|
33
|
+
content = content.replace(match[0], newLine);
|
|
34
|
+
fs.writeFileSync(skillPath, content, 'utf8');
|
|
35
|
+
console.log(`✅ Fixed ${skill}: ${originalDescription.substring(0, 30)}...`);
|
|
36
|
+
updated++;
|
|
37
|
+
} else {
|
|
38
|
+
// Check if it's already quoted
|
|
39
|
+
if (/^description: ".*"$/m.test(content)) {
|
|
40
|
+
console.log(`👌 ${skill} already quoted.`);
|
|
41
|
+
} else {
|
|
42
|
+
console.log(`⚠️ ${skill} description format not matching expected pattern.`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
console.log(`\nFixed ${updated} skills.`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
fixDescriptions();
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// ============================================================
|
|
3
|
+
// LMAgent Token Analyzer
|
|
4
|
+
// Mide el consumo de tokens del framework instalado en el proyecto
|
|
5
|
+
// Uso: node scripts/token-analyzer.js [--json] [--report]
|
|
6
|
+
// ============================================================
|
|
7
|
+
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
|
|
11
|
+
const PKG_VERSION = require('../package.json').version;
|
|
12
|
+
|
|
13
|
+
// Estimación estándar: ~4 chars = 1 token (GPT/Claude/Gemini)
|
|
14
|
+
function estimateTokens(text) {
|
|
15
|
+
return Math.ceil(text.length / 4);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function fileSizeKB(bytes) {
|
|
19
|
+
return (bytes / 1024).toFixed(1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function scanDir(dir, extensions = ['.md', '.txt', '.mdc', '.toml', '.cursorrules']) {
|
|
23
|
+
const results = [];
|
|
24
|
+
if (!fs.existsSync(dir)) return results;
|
|
25
|
+
const walk = (current) => {
|
|
26
|
+
for (const item of fs.readdirSync(current)) {
|
|
27
|
+
const full = path.join(current, item);
|
|
28
|
+
const stat = fs.statSync(full);
|
|
29
|
+
if (stat.isDirectory()) {
|
|
30
|
+
walk(full);
|
|
31
|
+
} else if (extensions.some(ext => item.endsWith(ext))) {
|
|
32
|
+
const content = fs.readFileSync(full, 'utf8');
|
|
33
|
+
results.push({
|
|
34
|
+
file: path.relative(process.cwd(), full),
|
|
35
|
+
bytes: stat.size,
|
|
36
|
+
tokens: estimateTokens(content),
|
|
37
|
+
chars: content.length,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
walk(dir);
|
|
43
|
+
return results;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function sumTokens(items) {
|
|
47
|
+
return items.reduce((acc, i) => acc + i.tokens, 0);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function sumBytes(items) {
|
|
51
|
+
return items.reduce((acc, i) => acc + i.bytes, 0);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function formatNum(n) {
|
|
55
|
+
return n.toLocaleString('en-US');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function padEnd(str, len) {
|
|
59
|
+
return String(str).padEnd(len);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function padStart(str, len) {
|
|
63
|
+
return String(str).padStart(len);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function row(label, files, tokens, kb) {
|
|
67
|
+
return `│ ${padEnd(label, 21)}│ ${padStart(files, 8)} │ ${padStart('~' + formatNum(tokens), 9)} │ ${padStart(kb + ' KB', 8)} │`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function divider() {
|
|
71
|
+
return '├───────────────────────┼──────────┼───────────┼──────────┤';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function analyze(options = {}) {
|
|
75
|
+
const projectRoot = process.cwd();
|
|
76
|
+
|
|
77
|
+
// Detectar qué agentes están instalados
|
|
78
|
+
const IDE_CONFIGS = require('../install.js'); // No disponible como export, usamos detección manual
|
|
79
|
+
const agentDirs = [
|
|
80
|
+
{ name: 'Cursor', rulesDir: '.cursor/rules', skillsDir: '.cursor/skills' },
|
|
81
|
+
{ name: 'Claude Code', rulesDir: '.claude/rules', skillsDir: '.claude/skills' },
|
|
82
|
+
{ name: 'Antigravity', rulesDir: '.agent/rules', skillsDir: '.agent/skills' },
|
|
83
|
+
{ name: 'Gemini CLI', rulesDir: '.gemini/rules', skillsDir: '.gemini/skills' },
|
|
84
|
+
{ name: 'Windsurf', rulesDir: '.windsurf/rules', skillsDir: '.windsurf/skills' },
|
|
85
|
+
{ name: 'Cline', rulesDir: '.clinerules', skillsDir: '.cline/skills' },
|
|
86
|
+
{ name: 'Roo Code', rulesDir: '.roo/rules', skillsDir: '.roo/skills' },
|
|
87
|
+
{ name: 'VSCode Copilot', rulesDir: '.github/instructions', skillsDir: '.github/skills' },
|
|
88
|
+
{ name: 'Augment', rulesDir: '.augment/rules', skillsDir: '.augment/skills' },
|
|
89
|
+
{ name: 'Continue', rulesDir: '.continue/rules', skillsDir: '.continue/skills' },
|
|
90
|
+
{ name: 'Codex', rulesDir: '.codex', skillsDir: '.codex/skills' },
|
|
91
|
+
{ name: 'OpenHands', rulesDir: '.openhands/microagents', skillsDir: '.openhands/skills' },
|
|
92
|
+
{ name: 'Junie', rulesDir: '.junie', skillsDir: '.junie/skills' },
|
|
93
|
+
{ name: 'Goose', rulesDir: '.goose', skillsDir: '.goose/skills' },
|
|
94
|
+
];
|
|
95
|
+
|
|
96
|
+
const installedAgents = agentDirs.filter(a =>
|
|
97
|
+
fs.existsSync(path.join(projectRoot, a.rulesDir)) ||
|
|
98
|
+
fs.existsSync(path.join(projectRoot, a.skillsDir))
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
// ── Entry Points (archivos raíz que el agente lee al arrancar) ──
|
|
102
|
+
const entryFiles = ['AGENTS.md', 'CLAUDE.md', 'GEMINI.md', '.cursorrules', '.continuerules', '.goosehints',
|
|
103
|
+
'.openhands/microagents/repo.md', '.junie/guidelines.md', '.github/copilot-instructions.md'];
|
|
104
|
+
const entryItems = entryFiles
|
|
105
|
+
.map(f => path.join(projectRoot, f))
|
|
106
|
+
.filter(f => fs.existsSync(f))
|
|
107
|
+
.map(f => {
|
|
108
|
+
const content = fs.readFileSync(f, 'utf8');
|
|
109
|
+
return { file: path.relative(projectRoot, f), bytes: fs.statSync(f).size, tokens: estimateTokens(content), chars: content.length };
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// ── Skills ──
|
|
113
|
+
const skillsItems = [];
|
|
114
|
+
for (const agent of installedAgents) {
|
|
115
|
+
const dir = path.join(projectRoot, agent.skillsDir);
|
|
116
|
+
skillsItems.push(...scanDir(dir));
|
|
117
|
+
}
|
|
118
|
+
// Deduplicar por contenido (mismo archivo copiado a múltiples agentes)
|
|
119
|
+
const skillsSeen = new Set();
|
|
120
|
+
const skillsUniq = skillsItems.filter(i => {
|
|
121
|
+
const key = path.basename(path.dirname(i.file)) + '/' + path.basename(i.file);
|
|
122
|
+
if (skillsSeen.has(key)) return false;
|
|
123
|
+
skillsSeen.add(key);
|
|
124
|
+
return true;
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// ── Rules ──
|
|
128
|
+
const rulesItems = [];
|
|
129
|
+
for (const agent of installedAgents) {
|
|
130
|
+
const dir = path.join(projectRoot, agent.rulesDir);
|
|
131
|
+
rulesItems.push(...scanDir(dir));
|
|
132
|
+
}
|
|
133
|
+
const rulesSeen = new Set();
|
|
134
|
+
const rulesUniq = rulesItems.filter(i => {
|
|
135
|
+
const key = path.basename(i.file);
|
|
136
|
+
if (rulesSeen.has(key)) return false;
|
|
137
|
+
rulesSeen.add(key);
|
|
138
|
+
return true;
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// ── Workflows ──
|
|
142
|
+
const workflowsItems = [];
|
|
143
|
+
const workflowDirs = installedAgents.map(a => a.skillsDir.replace('/skills', '/workflows'));
|
|
144
|
+
for (const dir of workflowDirs) {
|
|
145
|
+
workflowsItems.push(...scanDir(path.join(projectRoot, dir)));
|
|
146
|
+
}
|
|
147
|
+
const wfSeen = new Set();
|
|
148
|
+
const workflowsUniq = workflowsItems.filter(i => {
|
|
149
|
+
const key = path.basename(i.file);
|
|
150
|
+
if (wfSeen.has(key)) return false;
|
|
151
|
+
wfSeen.add(key);
|
|
152
|
+
return true;
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// ── Totales ──
|
|
156
|
+
const totalTokens = sumTokens(entryItems) + sumTokens(skillsUniq) + sumTokens(rulesUniq) + sumTokens(workflowsUniq);
|
|
157
|
+
const totalFiles = entryItems.length + skillsUniq.length + rulesUniq.length + workflowsUniq.length;
|
|
158
|
+
const totalBytes = sumBytes(entryItems) + sumBytes(skillsUniq) + sumBytes(rulesUniq) + sumBytes(workflowsUniq);
|
|
159
|
+
|
|
160
|
+
// ── Overhead real por sesión ──
|
|
161
|
+
// Solo se carga: entry point + 1 skill activo (on-demand)
|
|
162
|
+
const avgEntryTokens = sumTokens(entryItems);
|
|
163
|
+
const avgSkillTokens = skillsUniq.length > 0
|
|
164
|
+
? Math.round(sumTokens(skillsUniq) / skillsUniq.length)
|
|
165
|
+
: 0;
|
|
166
|
+
const sessionOverhead = avgEntryTokens + avgSkillTokens;
|
|
167
|
+
|
|
168
|
+
const data = {
|
|
169
|
+
version: PKG_VERSION,
|
|
170
|
+
installedAgents: installedAgents.map(a => a.name),
|
|
171
|
+
categories: {
|
|
172
|
+
entryPoints: { files: entryItems.length, tokens: sumTokens(entryItems), bytes: sumBytes(entryItems) },
|
|
173
|
+
skills: { files: skillsUniq.length, tokens: sumTokens(skillsUniq), bytes: sumBytes(skillsUniq) },
|
|
174
|
+
rules: { files: rulesUniq.length, tokens: sumTokens(rulesUniq), bytes: sumBytes(rulesUniq) },
|
|
175
|
+
workflows: { files: workflowsUniq.length, tokens: sumTokens(workflowsUniq), bytes: sumBytes(workflowsUniq) },
|
|
176
|
+
},
|
|
177
|
+
total: { files: totalFiles, tokens: totalTokens, bytes: totalBytes },
|
|
178
|
+
sessionOverhead: { tokens: sessionOverhead, entryPoints: avgEntryTokens, avgSkill: avgSkillTokens },
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
if (options.json) {
|
|
182
|
+
console.log(JSON.stringify(data, null, 2));
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// ── Tabla visual ──
|
|
187
|
+
console.log('');
|
|
188
|
+
console.log('╔══════════════════════════════════════════════════════╗');
|
|
189
|
+
console.log(`║ LMAgent Token Analyzer v${PKG_VERSION}${' '.repeat(Math.max(0, 22 - PKG_VERSION.length))}║`);
|
|
190
|
+
console.log('╚══════════════════════════════════════════════════════╝');
|
|
191
|
+
console.log('');
|
|
192
|
+
|
|
193
|
+
if (installedAgents.length === 0) {
|
|
194
|
+
console.log('⚠️ No se detectó ningún agente instalado en este proyecto.');
|
|
195
|
+
console.log(' Ejecuta `lmagent install` primero.');
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
console.log(`🤖 Agentes detectados: ${installedAgents.map(a => a.name).join(', ')}`);
|
|
200
|
+
console.log('');
|
|
201
|
+
console.log('📊 Análisis de Consumo de Tokens del Framework');
|
|
202
|
+
console.log('');
|
|
203
|
+
console.log('┌───────────────────────┬──────────┬───────────┬──────────┐');
|
|
204
|
+
console.log('│ Categoría │ Archivos │ Tokens │ Tamaño │');
|
|
205
|
+
console.log('├───────────────────────┼──────────┼───────────┼──────────┤');
|
|
206
|
+
console.log(row('Entry Points', entryItems.length, sumTokens(entryItems), fileSizeKB(sumBytes(entryItems))));
|
|
207
|
+
console.log(row(`Skills (${skillsUniq.length} únicos)`, skillsUniq.length, sumTokens(skillsUniq), fileSizeKB(sumBytes(skillsUniq))));
|
|
208
|
+
console.log(row('Rules', rulesUniq.length, sumTokens(rulesUniq), fileSizeKB(sumBytes(rulesUniq))));
|
|
209
|
+
console.log(row('Workflows', workflowsUniq.length, sumTokens(workflowsUniq), fileSizeKB(sumBytes(workflowsUniq))));
|
|
210
|
+
console.log('├───────────────────────┼──────────┼───────────┼──────────┤');
|
|
211
|
+
console.log(row('TOTAL FRAMEWORK', totalFiles, totalTokens, fileSizeKB(totalBytes)));
|
|
212
|
+
console.log('└───────────────────────┴──────────┴───────────┴──────────┘');
|
|
213
|
+
console.log('');
|
|
214
|
+
console.log('⚡ Overhead real por sesión (contexto auto-cargado):');
|
|
215
|
+
console.log('');
|
|
216
|
+
console.log(` Sin framework: 0 tokens de contexto de agente`);
|
|
217
|
+
console.log(` Entry points: ~${formatNum(avgEntryTokens)} tokens (leídos siempre)`);
|
|
218
|
+
console.log(` Skill activo: ~${formatNum(avgSkillTokens)} tokens promedio (on-demand)`);
|
|
219
|
+
console.log(` ─────────────────────────────────────────────────`);
|
|
220
|
+
console.log(` Overhead real: ~${formatNum(sessionOverhead)} tokens por sesión ← BAJO`);
|
|
221
|
+
console.log('');
|
|
222
|
+
console.log('💡 Los skills NO se cargan todos juntos. Solo se activa');
|
|
223
|
+
console.log(' el skill que el agente invoca (/dev, /front, /arch, etc.)');
|
|
224
|
+
console.log('');
|
|
225
|
+
|
|
226
|
+
if (options.report) {
|
|
227
|
+
generateReport(data, projectRoot);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function generateReport(data, projectRoot) {
|
|
232
|
+
const reportDir = path.join(projectRoot, '.agents');
|
|
233
|
+
if (!fs.existsSync(reportDir)) fs.mkdirSync(reportDir, { recursive: true });
|
|
234
|
+
|
|
235
|
+
const reportPath = path.join(reportDir, 'token-report.md');
|
|
236
|
+
const now = new Date().toISOString().split('T')[0];
|
|
237
|
+
|
|
238
|
+
const md = `# LMAgent Token Report v${data.version}
|
|
239
|
+
> Generado: ${now}
|
|
240
|
+
|
|
241
|
+
## Agentes instalados
|
|
242
|
+
${data.installedAgents.map(a => `- ${a}`).join('\n')}
|
|
243
|
+
|
|
244
|
+
## Consumo por categoría
|
|
245
|
+
|
|
246
|
+
| Categoría | Archivos | Tokens (est.) | Tamaño |
|
|
247
|
+
|:---|---:|---:|---:|
|
|
248
|
+
| Entry Points | ${data.categories.entryPoints.files} | ~${formatNum(data.categories.entryPoints.tokens)} | ${fileSizeKB(data.categories.entryPoints.bytes)} KB |
|
|
249
|
+
| Skills | ${data.categories.skills.files} | ~${formatNum(data.categories.skills.tokens)} | ${fileSizeKB(data.categories.skills.bytes)} KB |
|
|
250
|
+
| Rules | ${data.categories.rules.files} | ~${formatNum(data.categories.rules.tokens)} | ${fileSizeKB(data.categories.rules.bytes)} KB |
|
|
251
|
+
| Workflows | ${data.categories.workflows.files} | ~${formatNum(data.categories.workflows.tokens)} | ${fileSizeKB(data.categories.workflows.bytes)} KB |
|
|
252
|
+
| **TOTAL** | **${data.total.files}** | **~${formatNum(data.total.tokens)}** | **${fileSizeKB(data.total.bytes)} KB** |
|
|
253
|
+
|
|
254
|
+
## Overhead real por sesión
|
|
255
|
+
|
|
256
|
+
| Componente | Tokens |
|
|
257
|
+
|:---|---:|
|
|
258
|
+
| Entry Points (siempre cargados) | ~${formatNum(data.sessionOverhead.entryPoints)} |
|
|
259
|
+
| Skill activo (promedio, on-demand) | ~${formatNum(data.sessionOverhead.avgSkill)} |
|
|
260
|
+
| **Total overhead por sesión** | **~${formatNum(data.sessionOverhead.tokens)}** |
|
|
261
|
+
|
|
262
|
+
> Los skills **no** se cargan todos juntos. Solo se activa el skill que el agente invoca.
|
|
263
|
+
`;
|
|
264
|
+
|
|
265
|
+
fs.writeFileSync(reportPath, md, 'utf8');
|
|
266
|
+
console.log(`📄 Reporte guardado en: ${reportPath}`);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// CLI directo
|
|
270
|
+
const args = process.argv.slice(2);
|
|
271
|
+
const options = {
|
|
272
|
+
json: args.includes('--json'),
|
|
273
|
+
report: args.includes('--report'),
|
|
274
|
+
};
|
|
275
|
+
analyze(options);
|