@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("\n📚 Comandos disponibles:")
605
- console.log(" creta - Explora los 7 enunciados fundamentales (comando principal) 🧠")
606
- console.log(" creta enunciados - Explora los 7 enunciados fundamentales 🧠")
607
- console.log(" creta portafolio - Crea tu portafolio personal (reto completo)")
608
- console.log(" creta portafolio-1 - Desbloquea nivel 1 (navbar) 🔓")
609
- console.log(" creta portafolio-2 - Desbloquea nivel 2 (navbar + hero) 🔓")
610
- console.log(" creta portafolio-3 - Desbloquea nivel 3 (solución completa) 🔓")
611
- console.log(" creta code - Inicia sesión interactiva de programación 🤖")
612
- console.log(" creta help - Muestra esta ayuda")
613
- console.log("\n💡 Tip: Si estás dentro de un proyecto existente, los comandos")
614
- console.log(" portafolio-1/2/3 actualizarán tus archivos directamente")
615
- console.log("\n🎯 La filosofía Creta: partir de enunciados que generan 'ruido' para")
616
- console.log(" construir comprensión real, no solo sintaxis.")
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-2) o 'q' para salir: ")
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 2.")
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
  }