@icarusmx/creta 1.5.6 → 1.5.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/bin/creta.js
CHANGED
|
@@ -7,6 +7,8 @@ import path from 'path'
|
|
|
7
7
|
import { fileURLToPath } from 'url'
|
|
8
8
|
import { CretaCodeSession } from '../lib/session.js'
|
|
9
9
|
import { PullRequestTutorial } from '../lib/pr-tutorial.js'
|
|
10
|
+
import { VimSetupTutorial } from '../lib/vim-setup-tutorial.js'
|
|
11
|
+
import { AWSGuideViewer } from '../lib/aws-guide-viewer.js'
|
|
10
12
|
import { CommandHelpExecutor } from '../lib/executors/CommandHelpExecutor.js'
|
|
11
13
|
import { PapersExecutor } from '../lib/papers/PapersExecutor.js'
|
|
12
14
|
import { ExercisesExecutor } from '../lib/executors/ExercisesExecutor.js'
|
|
@@ -112,6 +114,10 @@ if (command.startsWith('portafolio')) {
|
|
|
112
114
|
// Start papers selector
|
|
113
115
|
const executor = new PapersExecutor()
|
|
114
116
|
await executor.execute()
|
|
117
|
+
} else if (command === 'aws') {
|
|
118
|
+
// Show AWS billing detective guide
|
|
119
|
+
const viewer = new AWSGuideViewer()
|
|
120
|
+
await viewer.start()
|
|
115
121
|
} else if (command === 'help' || command === 'ayuda') {
|
|
116
122
|
// Show available commands
|
|
117
123
|
showHelp()
|
|
@@ -601,19 +607,27 @@ async function unstuckProject(level) {
|
|
|
601
607
|
}
|
|
602
608
|
|
|
603
609
|
function showHelp() {
|
|
604
|
-
console.log("\
|
|
605
|
-
console.log("
|
|
606
|
-
console.log("
|
|
607
|
-
|
|
608
|
-
console.log("
|
|
609
|
-
console.log(" creta
|
|
610
|
-
console.log(" creta
|
|
611
|
-
console.log(" creta
|
|
612
|
-
console.log(" creta
|
|
613
|
-
|
|
614
|
-
console.log("
|
|
615
|
-
console.log("
|
|
616
|
-
console.log("
|
|
610
|
+
console.log("\nCreta es una herramienta de CLI desarrollada por icarus.mx.")
|
|
611
|
+
console.log("El propósito de esta herramienta es acompañar a aprendices y")
|
|
612
|
+
console.log("constructores del taller en su camino de aprendizaje.\n")
|
|
613
|
+
|
|
614
|
+
console.log("📖 Documentación de comandos:")
|
|
615
|
+
console.log(" creta ls - Documentación en español del comando ls")
|
|
616
|
+
console.log(" creta cd - Documentación en español del comando cd")
|
|
617
|
+
console.log(" creta git status - Documentación en español de git status")
|
|
618
|
+
console.log(" creta [comando] - Documentación de cualquier comando soportado\n")
|
|
619
|
+
|
|
620
|
+
console.log("🎓 Aprendizaje:")
|
|
621
|
+
console.log(" creta - Menú principal interactivo")
|
|
622
|
+
console.log(" creta sintaxis - Aprende comandos de terminal y Git")
|
|
623
|
+
console.log(" creta enunciados - Explora los 7 enunciados fundamentales de OOP")
|
|
624
|
+
console.log(" creta papers - Recrea papers clásicos de Computer Science\n")
|
|
625
|
+
|
|
626
|
+
console.log("🛠️ Proyectos:")
|
|
627
|
+
console.log(" creta portafolio - Crea tu portafolio personal")
|
|
628
|
+
console.log(" creta code - Sesión interactiva de programación con IA\n")
|
|
629
|
+
|
|
630
|
+
console.log("💡 Usa 'creta' sin argumentos para ver el menú principal.")
|
|
617
631
|
}
|
|
618
632
|
|
|
619
633
|
async function startMainMenu() {
|
|
@@ -720,6 +734,46 @@ Salgamos de este laberinto 🏛️
|
|
|
720
734
|
return
|
|
721
735
|
}
|
|
722
736
|
|
|
737
|
+
// Handle vim-style navigation
|
|
738
|
+
if (key === 'k' || key === 'K') { // Vim: up
|
|
739
|
+
selectedIndex = selectedIndex > 0 ? selectedIndex - 1 : options.length - 1
|
|
740
|
+
renderOptions()
|
|
741
|
+
return
|
|
742
|
+
} else if (key === 'j' || key === 'J') { // Vim: down
|
|
743
|
+
selectedIndex = selectedIndex < options.length - 1 ? selectedIndex + 1 : 0
|
|
744
|
+
renderOptions()
|
|
745
|
+
return
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
// Handle number key selection
|
|
749
|
+
const num = parseInt(key)
|
|
750
|
+
if (num >= 1 && num <= options.length) {
|
|
751
|
+
selectedIndex = num - 1
|
|
752
|
+
process.stdin.setRawMode(false)
|
|
753
|
+
process.stdin.removeListener('data', onKeyPress)
|
|
754
|
+
|
|
755
|
+
const selectedOption = options[selectedIndex]
|
|
756
|
+
|
|
757
|
+
// Clear screen
|
|
758
|
+
process.stdout.write('\x1b[2J')
|
|
759
|
+
process.stdout.write('\x1b[H')
|
|
760
|
+
|
|
761
|
+
if (selectedOption.id === 1) {
|
|
762
|
+
startSintaxisSelector().then(resolve)
|
|
763
|
+
} else if (selectedOption.id === 2) {
|
|
764
|
+
startEnunciadosSelector().then(resolve)
|
|
765
|
+
} else if (selectedOption.id === 3) {
|
|
766
|
+
startProyectosSelector().then(resolve)
|
|
767
|
+
} else if (selectedOption.id === 4) {
|
|
768
|
+
const executor = new PapersExecutor()
|
|
769
|
+
executor.execute().then(resolve)
|
|
770
|
+
} else if (selectedOption.id === 5) {
|
|
771
|
+
const executor = new ExercisesExecutor()
|
|
772
|
+
executor.execute().then(resolve)
|
|
773
|
+
}
|
|
774
|
+
return
|
|
775
|
+
}
|
|
776
|
+
|
|
723
777
|
// Handle arrow keys (escape sequences)
|
|
724
778
|
if (key === '\u001b[A') { // Up arrow
|
|
725
779
|
selectedIndex = selectedIndex > 0 ? selectedIndex - 1 : options.length - 1
|
|
@@ -1040,6 +1094,27 @@ async function startEnunciadosSelectorInteractive() {
|
|
|
1040
1094
|
return
|
|
1041
1095
|
}
|
|
1042
1096
|
|
|
1097
|
+
// Handle vim-style navigation
|
|
1098
|
+
if (key === 'k' || key === 'K') { // Vim: up
|
|
1099
|
+
selectedIndex = selectedIndex > 0 ? selectedIndex - 1 : ENUNCIADOS.length - 1
|
|
1100
|
+
renderOptions()
|
|
1101
|
+
return
|
|
1102
|
+
} else if (key === 'j' || key === 'J') { // Vim: down
|
|
1103
|
+
selectedIndex = selectedIndex < ENUNCIADOS.length - 1 ? selectedIndex + 1 : 0
|
|
1104
|
+
renderOptions()
|
|
1105
|
+
return
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
// Handle number key selection (1-7)
|
|
1109
|
+
const num = parseInt(key)
|
|
1110
|
+
if (num >= 1 && num <= ENUNCIADOS.length) {
|
|
1111
|
+
selectedIndex = num - 1
|
|
1112
|
+
// Simulate Enter key press to execute the selected option
|
|
1113
|
+
const enterEvent = '\r'
|
|
1114
|
+
onKeyPress(enterEvent)
|
|
1115
|
+
return
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1043
1118
|
// Handle arrow keys (escape sequences)
|
|
1044
1119
|
if (key === '\u001b[A') { // Up arrow
|
|
1045
1120
|
selectedIndex = selectedIndex > 0 ? selectedIndex - 1 : ENUNCIADOS.length - 1
|
|
@@ -1306,7 +1381,8 @@ async function startProyectosSelectorInteractive() {
|
|
|
1306
1381
|
|
|
1307
1382
|
const projects = [
|
|
1308
1383
|
{ id: 1, title: "🔀 Construye una pull-request", type: 'pr' },
|
|
1309
|
-
{ id: 2, title: "🎨 Construye tu portafolio", type: 'portfolio' }
|
|
1384
|
+
{ id: 2, title: "🎨 Construye tu portafolio", type: 'portfolio' },
|
|
1385
|
+
{ id: 3, title: "⌨️ Configura Creta Vim", type: 'vim' }
|
|
1310
1386
|
]
|
|
1311
1387
|
|
|
1312
1388
|
let selectedIndex = 0
|
|
@@ -1365,6 +1441,42 @@ async function startProyectosSelectorInteractive() {
|
|
|
1365
1441
|
startPullRequestTutorial().then(resolve)
|
|
1366
1442
|
} else if (selectedProject.type === 'portfolio') {
|
|
1367
1443
|
startPortfolioSelector().then(resolve)
|
|
1444
|
+
} else if (selectedProject.type === 'vim') {
|
|
1445
|
+
startVimSetupTutorial().then(resolve)
|
|
1446
|
+
}
|
|
1447
|
+
return
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
// Handle vim-style navigation
|
|
1451
|
+
if (key === 'k' || key === 'K') { // Vim: up
|
|
1452
|
+
selectedIndex = selectedIndex > 0 ? selectedIndex - 1 : projects.length - 1
|
|
1453
|
+
renderOptions()
|
|
1454
|
+
return
|
|
1455
|
+
} else if (key === 'j' || key === 'J') { // Vim: down
|
|
1456
|
+
selectedIndex = selectedIndex < projects.length - 1 ? selectedIndex + 1 : 0
|
|
1457
|
+
renderOptions()
|
|
1458
|
+
return
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
// Handle number key selection
|
|
1462
|
+
const num = parseInt(key)
|
|
1463
|
+
if (num >= 1 && num <= projects.length) {
|
|
1464
|
+
selectedIndex = num - 1
|
|
1465
|
+
process.stdin.setRawMode(false)
|
|
1466
|
+
process.stdin.removeListener('data', onKeyPress)
|
|
1467
|
+
|
|
1468
|
+
const selectedProject = projects[selectedIndex]
|
|
1469
|
+
|
|
1470
|
+
// Clear screen
|
|
1471
|
+
process.stdout.write('\x1b[2J')
|
|
1472
|
+
process.stdout.write('\x1b[H')
|
|
1473
|
+
|
|
1474
|
+
if (selectedProject.type === 'pr') {
|
|
1475
|
+
startPullRequestTutorial().then(resolve)
|
|
1476
|
+
} else if (selectedProject.type === 'portfolio') {
|
|
1477
|
+
startPortfolioSelector().then(resolve)
|
|
1478
|
+
} else if (selectedProject.type === 'vim') {
|
|
1479
|
+
startVimSetupTutorial().then(resolve)
|
|
1368
1480
|
}
|
|
1369
1481
|
return
|
|
1370
1482
|
}
|
|
@@ -1429,9 +1541,10 @@ async function startProyectosSelectorFallback() {
|
|
|
1429
1541
|
console.log("")
|
|
1430
1542
|
console.log("1. 🔀 Construye una pull-request")
|
|
1431
1543
|
console.log("2. 🎨 Construye tu portafolio")
|
|
1544
|
+
console.log("3. ⌨️ Configura Creta Vim")
|
|
1432
1545
|
console.log("")
|
|
1433
1546
|
|
|
1434
|
-
const respuesta = await askQuestion("Elige una opción (1-
|
|
1547
|
+
const respuesta = await askQuestion("Elige una opción (1-3) o 'q' para salir: ")
|
|
1435
1548
|
|
|
1436
1549
|
if (respuesta.toLowerCase() === 'q') {
|
|
1437
1550
|
console.log("Hecho con <3 por icarus.mx")
|
|
@@ -1447,8 +1560,11 @@ async function startProyectosSelectorFallback() {
|
|
|
1447
1560
|
} else if (opcionSeleccionada === 2) {
|
|
1448
1561
|
rl.close()
|
|
1449
1562
|
await startPortfolioSelector()
|
|
1563
|
+
} else if (opcionSeleccionada === 3) {
|
|
1564
|
+
rl.close()
|
|
1565
|
+
await startVimSetupTutorial()
|
|
1450
1566
|
} else {
|
|
1451
|
-
console.log("❌ Opción no válida. Elige 1 o
|
|
1567
|
+
console.log("❌ Opción no válida. Elige 1, 2 o 3.")
|
|
1452
1568
|
rl.close()
|
|
1453
1569
|
}
|
|
1454
1570
|
|
|
@@ -1551,6 +1667,40 @@ async function startPortfolioSelectorInteractive() {
|
|
|
1551
1667
|
return
|
|
1552
1668
|
}
|
|
1553
1669
|
|
|
1670
|
+
// Handle vim-style navigation
|
|
1671
|
+
if (key === 'k' || key === 'K') { // Vim: up
|
|
1672
|
+
selectedIndex = selectedIndex > 0 ? selectedIndex - 1 : projects.length - 1
|
|
1673
|
+
renderOptions()
|
|
1674
|
+
return
|
|
1675
|
+
} else if (key === 'j' || key === 'J') { // Vim: down
|
|
1676
|
+
selectedIndex = selectedIndex < projects.length - 1 ? selectedIndex + 1 : 0
|
|
1677
|
+
renderOptions()
|
|
1678
|
+
return
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
// Handle number key selection
|
|
1682
|
+
const num = parseInt(key)
|
|
1683
|
+
if (num >= 1 && num <= projects.length) {
|
|
1684
|
+
selectedIndex = num - 1
|
|
1685
|
+
process.stdin.setRawMode(false)
|
|
1686
|
+
process.stdin.removeListener('data', onKeyPress)
|
|
1687
|
+
|
|
1688
|
+
const selectedProject = projects[selectedIndex]
|
|
1689
|
+
const level = selectedProject.level
|
|
1690
|
+
|
|
1691
|
+
// Clear screen
|
|
1692
|
+
process.stdout.write('\x1b[2J')
|
|
1693
|
+
process.stdout.write('\x1b[H')
|
|
1694
|
+
|
|
1695
|
+
// Check if we're in an existing Creta project
|
|
1696
|
+
if (level > 0 && isInCretaProject()) {
|
|
1697
|
+
unstuckProject(level).then(resolve)
|
|
1698
|
+
} else {
|
|
1699
|
+
createPortfolioProject(level).then(resolve)
|
|
1700
|
+
}
|
|
1701
|
+
return
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1554
1704
|
// Handle arrow keys (escape sequences)
|
|
1555
1705
|
if (key === '\u001b[A') { // Up arrow
|
|
1556
1706
|
selectedIndex = selectedIndex > 0 ? selectedIndex - 1 : projects.length - 1
|
|
@@ -1648,6 +1798,33 @@ async function startPullRequestTutorial() {
|
|
|
1648
1798
|
}
|
|
1649
1799
|
}
|
|
1650
1800
|
|
|
1801
|
+
async function startVimSetupTutorial() {
|
|
1802
|
+
try {
|
|
1803
|
+
const tutorial = new VimSetupTutorial()
|
|
1804
|
+
await tutorial.start()
|
|
1805
|
+
|
|
1806
|
+
// Volver al menú principal después de completar el tutorial
|
|
1807
|
+
await returnToMainMenu()
|
|
1808
|
+
} catch (error) {
|
|
1809
|
+
console.error("\n❌ Error al ejecutar el tutorial:", error.message)
|
|
1810
|
+
console.log("\nSi el problema persiste, contacta al equipo de Creta.")
|
|
1811
|
+
|
|
1812
|
+
const rl = createInterface({
|
|
1813
|
+
input: process.stdin,
|
|
1814
|
+
output: process.stdout
|
|
1815
|
+
})
|
|
1816
|
+
|
|
1817
|
+
await new Promise((resolve) => {
|
|
1818
|
+
rl.question("\nPresiona Enter para volver al menú principal...", () => {
|
|
1819
|
+
rl.close()
|
|
1820
|
+
resolve()
|
|
1821
|
+
})
|
|
1822
|
+
})
|
|
1823
|
+
|
|
1824
|
+
await startMainMenu()
|
|
1825
|
+
}
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1651
1828
|
// Helper function to detect command help requests
|
|
1652
1829
|
function isCommandHelpRequest(args) {
|
|
1653
1830
|
// List of supported commands for help
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { createInterface } from 'readline'
|
|
2
|
+
import { execSync } from 'child_process'
|
|
3
|
+
import fs from 'fs'
|
|
4
|
+
import path from 'path'
|
|
5
|
+
import { fileURLToPath } from 'url'
|
|
6
|
+
import chalk from 'chalk'
|
|
7
|
+
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
9
|
+
const __dirname = path.dirname(__filename)
|
|
10
|
+
|
|
11
|
+
export class AWSGuideViewer {
|
|
12
|
+
constructor() {
|
|
13
|
+
this.guidePath = path.join(__dirname, 'exercises', 'aws-billing-detective.md')
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async start() {
|
|
17
|
+
console.clear()
|
|
18
|
+
this.showWelcome()
|
|
19
|
+
await this.showOptions()
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
showWelcome() {
|
|
23
|
+
console.log(chalk.cyan('\n🕵️ AWS Billing Detective'))
|
|
24
|
+
console.log(chalk.gray('═'.repeat(60)))
|
|
25
|
+
console.log('\n' + chalk.bold('El misterio: "Apagué todo pero AWS me sigue cobrando"'))
|
|
26
|
+
console.log('\nGuía completa para encontrar cargos ocultos de AWS:')
|
|
27
|
+
console.log(' • Obtener credenciales de AWS (IAM)')
|
|
28
|
+
console.log(' • Instalar y configurar AWS CLI')
|
|
29
|
+
console.log(' • Comandos de auditoría región por región')
|
|
30
|
+
console.log(' • Los sospechosos habituales (EBS, Elastic IPs, NAT Gateways)')
|
|
31
|
+
console.log(' • Scripts de prevención')
|
|
32
|
+
console.log('\n' + chalk.yellow('📍 Ubicación: ') + this.guidePath)
|
|
33
|
+
console.log(chalk.gray('\n588 líneas de guía paso a paso\n'))
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async showOptions() {
|
|
37
|
+
const rl = createInterface({
|
|
38
|
+
input: process.stdin,
|
|
39
|
+
output: process.stdout
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
const askQuestion = (question) => {
|
|
43
|
+
return new Promise((resolve) => {
|
|
44
|
+
rl.question(question, resolve)
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Check if file exists
|
|
49
|
+
if (!fs.existsSync(this.guidePath)) {
|
|
50
|
+
console.log(chalk.red('❌ Error: No se encontró la guía de AWS'))
|
|
51
|
+
console.log(chalk.gray('\nAsegúrate de estar ejecutando Creta desde el directorio correcto'))
|
|
52
|
+
rl.close()
|
|
53
|
+
return
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
console.log(chalk.bold('📚 Opciones:'))
|
|
57
|
+
console.log('')
|
|
58
|
+
console.log('1. Abrir con nvim ' + chalk.gray('(recomendado, usa fold markers)'))
|
|
59
|
+
console.log('2. Mostrar la ruta ' + chalk.gray('(para copiar)'))
|
|
60
|
+
console.log('3. Ver primeros 30 líneas ' + chalk.gray('(preview)'))
|
|
61
|
+
console.log('4. Ver tabla de contenido')
|
|
62
|
+
console.log('')
|
|
63
|
+
|
|
64
|
+
const choice = await askQuestion(chalk.cyan('Elige una opción (1-4) o Enter para nvim: '))
|
|
65
|
+
rl.close()
|
|
66
|
+
|
|
67
|
+
if (choice === '' || choice === '1') {
|
|
68
|
+
await this.openWithNvim()
|
|
69
|
+
} else if (choice === '2') {
|
|
70
|
+
this.showPath()
|
|
71
|
+
} else if (choice === '3') {
|
|
72
|
+
this.showPreview()
|
|
73
|
+
} else if (choice === '4') {
|
|
74
|
+
this.showTableOfContents()
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async openWithNvim() {
|
|
79
|
+
console.log('\n' + chalk.cyan('🚀 Abriendo con nvim...'))
|
|
80
|
+
console.log(chalk.gray('💡 Tip: Usa ') + chalk.yellow('zM') + chalk.gray(' para cerrar todos los folds'))
|
|
81
|
+
console.log(chalk.gray(' Usa ') + chalk.yellow('zR') + chalk.gray(' para abrir todos los folds'))
|
|
82
|
+
console.log(chalk.gray(' Usa ') + chalk.yellow('za') + chalk.gray(' para toggle fold en la línea actual'))
|
|
83
|
+
console.log('')
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
execSync(`nvim "${this.guidePath}"`, { stdio: 'inherit' })
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.log('\n' + chalk.yellow('⚠️ No se pudo abrir nvim. Intentando con el editor por defecto...'))
|
|
89
|
+
try {
|
|
90
|
+
execSync(`$EDITOR "${this.guidePath}" || vi "${this.guidePath}"`, { stdio: 'inherit' })
|
|
91
|
+
} catch (fallbackError) {
|
|
92
|
+
console.log('\n' + chalk.red('❌ No se pudo abrir ningún editor.'))
|
|
93
|
+
console.log(chalk.yellow('📍 Abre manualmente: ') + this.guidePath)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
showPath() {
|
|
99
|
+
console.log('\n' + chalk.cyan('📋 Ruta del archivo:'))
|
|
100
|
+
console.log(chalk.yellow(this.guidePath))
|
|
101
|
+
console.log('')
|
|
102
|
+
console.log(chalk.gray('💡 Tip: Usa este comando para abrirlo:'))
|
|
103
|
+
console.log(chalk.yellow(` nvim "${this.guidePath}"`))
|
|
104
|
+
console.log('')
|
|
105
|
+
|
|
106
|
+
// Try to copy to clipboard (macOS)
|
|
107
|
+
try {
|
|
108
|
+
execSync(`echo "${this.guidePath}" | pbcopy`, { stdio: 'ignore' })
|
|
109
|
+
console.log(chalk.green('✓ Ruta copiada al portapapeles (macOS)'))
|
|
110
|
+
} catch (error) {
|
|
111
|
+
// Silently fail if pbcopy not available
|
|
112
|
+
}
|
|
113
|
+
console.log('')
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
showPreview() {
|
|
117
|
+
console.log('\n' + chalk.cyan('📄 Primeras 30 líneas:'))
|
|
118
|
+
console.log(chalk.gray('═'.repeat(60)))
|
|
119
|
+
try {
|
|
120
|
+
const preview = execSync(`head -30 "${this.guidePath}"`, { encoding: 'utf8' })
|
|
121
|
+
console.log(preview)
|
|
122
|
+
} catch (error) {
|
|
123
|
+
console.log(chalk.red('❌ Error al leer el archivo'))
|
|
124
|
+
}
|
|
125
|
+
console.log(chalk.gray('═'.repeat(60)))
|
|
126
|
+
console.log('')
|
|
127
|
+
console.log(chalk.gray('💡 Abre el archivo completo con: ') + chalk.yellow(`creta aws`))
|
|
128
|
+
console.log(chalk.gray(' O directamente: ') + chalk.yellow(`nvim "${this.guidePath}"`))
|
|
129
|
+
console.log('')
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
showTableOfContents() {
|
|
133
|
+
console.log('\n' + chalk.cyan('📚 Tabla de Contenido'))
|
|
134
|
+
console.log(chalk.gray('═'.repeat(60)))
|
|
135
|
+
console.log('')
|
|
136
|
+
|
|
137
|
+
const toc = [
|
|
138
|
+
{ section: 'El Misterio', description: 'Por qué AWS cobra después de apagar todo' },
|
|
139
|
+
{ section: 'Parte 1', description: 'Obtener tus AWS Access Keys del console' },
|
|
140
|
+
{ section: 'Parte 2', description: 'Instalar y configurar AWS CLI' },
|
|
141
|
+
{ section: 'Parte 3', description: 'Auditoría completa de billing' },
|
|
142
|
+
{ section: '', description: ' • EC2 instances' },
|
|
143
|
+
{ section: '', description: ' • EBS volumes (el culpable #1)' },
|
|
144
|
+
{ section: '', description: ' • Elastic IPs ($3.60/mes cada una)' },
|
|
145
|
+
{ section: '', description: ' • NAT Gateways ($32/mes cada uno!)' },
|
|
146
|
+
{ section: '', description: ' • Load Balancers' },
|
|
147
|
+
{ section: '', description: ' • RDS databases' },
|
|
148
|
+
{ section: '', description: ' • Snapshots' },
|
|
149
|
+
{ section: '', description: ' • S3 buckets' },
|
|
150
|
+
{ section: '', description: ' • CloudWatch logs' },
|
|
151
|
+
{ section: 'Parte 4', description: 'Script de auditoría multi-región' },
|
|
152
|
+
{ section: 'Parte 5', description: 'Cost Explorer (interfaz web)' },
|
|
153
|
+
{ section: 'Parte 6', description: 'Alertas de billing (prevención)' },
|
|
154
|
+
{ section: 'Quick Reference', description: 'Comandos más comunes' },
|
|
155
|
+
{ section: 'Troubleshooting', description: 'Solución de problemas' },
|
|
156
|
+
{ section: 'Checklist', description: 'Lista de prevención' },
|
|
157
|
+
]
|
|
158
|
+
|
|
159
|
+
toc.forEach(({ section, description }) => {
|
|
160
|
+
if (section) {
|
|
161
|
+
console.log(chalk.bold(section.padEnd(15)) + chalk.gray(description))
|
|
162
|
+
} else {
|
|
163
|
+
console.log(chalk.gray(description))
|
|
164
|
+
}
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
console.log('')
|
|
168
|
+
console.log(chalk.gray('─'.repeat(60)))
|
|
169
|
+
console.log('')
|
|
170
|
+
console.log(chalk.yellow('💡 La guía completa tiene 588 líneas con:'))
|
|
171
|
+
console.log(' • Comandos copy-paste listos para usar')
|
|
172
|
+
console.log(' • Explicaciones de qué hace cada servicio')
|
|
173
|
+
console.log(' • Estimaciones de costo por servicio')
|
|
174
|
+
console.log(' • Scripts para automatizar la auditoría')
|
|
175
|
+
console.log('')
|
|
176
|
+
console.log(chalk.gray('Abre con: ') + chalk.yellow('nvim "' + this.guidePath + '"'))
|
|
177
|
+
console.log('')
|
|
178
|
+
}
|
|
179
|
+
}
|
|
@@ -33,6 +33,14 @@ export class ExercisesExecutor {
|
|
|
33
33
|
file: "railway-deployment.md",
|
|
34
34
|
lines: 500,
|
|
35
35
|
topics: ["Railway", "Deployment", "Node.js", "DevOps"]
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
id: 4,
|
|
39
|
+
title: "AWS Billing Detective",
|
|
40
|
+
description: "Hunt down AWS charges and prevent surprises",
|
|
41
|
+
file: "aws-billing-detective.md",
|
|
42
|
+
lines: 588,
|
|
43
|
+
topics: ["AWS", "CLI", "Cost Management", "DevOps"]
|
|
36
44
|
}
|
|
37
45
|
]
|
|
38
46
|
}
|