@icarusmx/creta 1.4.19 → 1.5.3

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.
@@ -0,0 +1,43 @@
1
+ export const wc = {
2
+ command: 'wc',
3
+ description: 'Cuenta líneas, palabras y caracteres en archivos (Word Count)',
4
+ usage: 'wc [opciones] archivo',
5
+ commonOptions: [
6
+ {
7
+ flag: '-l',
8
+ description: 'Solo contar líneas'
9
+ },
10
+ {
11
+ flag: '-w',
12
+ description: 'Solo contar palabras'
13
+ },
14
+ {
15
+ flag: '-c',
16
+ description: 'Solo contar caracteres (bytes)'
17
+ }
18
+ ],
19
+ examples: [
20
+ {
21
+ command: 'wc archivo.txt',
22
+ description: 'Contar líneas, palabras y caracteres'
23
+ },
24
+ {
25
+ command: 'wc -l archivo.txt',
26
+ description: 'Solo contar cuántas líneas tiene el archivo'
27
+ },
28
+ {
29
+ command: 'ls | wc -l',
30
+ description: 'Contar cuántos archivos hay en el directorio'
31
+ },
32
+ {
33
+ command: 'wc -l *.js',
34
+ description: 'Contar líneas de todos los archivos .js'
35
+ }
36
+ ],
37
+ relatedLesson: 'Piping y redirección',
38
+ tips: [
39
+ 'Muy útil con pipes para contar resultados',
40
+ 'Ejemplo: git log --oneline | wc -l = contar commits',
41
+ 'Puedes usar wc con múltiples archivos para ver totales'
42
+ ]
43
+ }
@@ -11,17 +11,29 @@ Salgamos de este laberinto 🏛️
11
11
 
12
12
  export const HELP_TEXT = `
13
13
  📚 Comandos disponibles:
14
- creta - Explora los 7 enunciados fundamentales (comando principal) 🧠
15
- creta enunciados - Explora los 7 enunciados fundamentales 🧠
14
+
15
+ 🎓 Aprendizaje:
16
+ creta - Menú principal (recomendado para empezar)
17
+ creta sintaxis - Aprende terminal, Git, piping y scripts (¡con práctica interactiva!) 💻
18
+ creta enunciados - Explora los 7 enunciados fundamentales (conceptos OOP) 🧠
19
+ creta proyectos - Ver proyectos disponibles
20
+
21
+ 🚀 Portafolios:
16
22
  creta portafolio - Crea tu portafolio personal (reto completo)
17
23
  creta portafolio-1 - Desbloquea nivel 1 (navbar) 🔓
18
24
  creta portafolio-2 - Desbloquea nivel 2 (navbar + hero) 🔓
19
25
  creta portafolio-3 - Desbloquea nivel 3 (solución completa) 🔓
26
+
27
+ 🛠️ Utilidades:
20
28
  creta code - Inicia sesión interactiva de programación 🤖
29
+ creta reset - Reinicia tu estado de usuario (nombre, progreso) 🗑️
30
+ creta icarus-terminal - Personaliza tu terminal con tema de Icarus
31
+ creta revert-terminal - Revertir personalización de terminal
21
32
  creta help - Muestra esta ayuda
22
33
 
23
- 💡 Tip: Si estás dentro de un proyecto existente, los comandos
24
- portafolio-1/2/3 actualizarán tus archivos directamente
34
+ 💾 Datos locales:
35
+ ~/.creta/user.json - Tu perfil (usa 'creta reset' para borrar)
36
+ /tmp/creta-practice-* - Sandboxes temporales (limpieza automática)
25
37
 
26
38
  🎯 La filosofía Creta: partir de enunciados que generan 'ruido' para
27
39
  construir comprensión real, no solo sintaxis.
@@ -0,0 +1,93 @@
1
+ import chalk from 'chalk'
2
+ import * as commandHelp from '../data/command-help/index.js'
3
+
4
+ export class CommandHelpExecutor {
5
+ constructor(commandArgs) {
6
+ this.commandArgs = commandArgs // e.g., ['ls'] or ['git', 'status']
7
+ }
8
+
9
+ async execute() {
10
+ const commandKey = this.commandArgs.join(' ')
11
+ const helpDataKey = commandHelp.commandMap[commandKey]
12
+
13
+ if (!helpDataKey) {
14
+ this.showNotFound(commandKey)
15
+ return
16
+ }
17
+
18
+ const helpData = commandHelp[helpDataKey]
19
+ this.renderHelp(helpData)
20
+ }
21
+
22
+ renderHelp(data) {
23
+ console.log('')
24
+ console.log(chalk.bold.cyan(`📘 ${data.command}`))
25
+ console.log(chalk.dim('─'.repeat(60)))
26
+ console.log('')
27
+
28
+ // Description
29
+ console.log(chalk.white(data.description))
30
+ console.log('')
31
+
32
+ // Usage
33
+ console.log(chalk.bold('Uso:'))
34
+ console.log(chalk.yellow(` ${data.usage}`))
35
+ console.log('')
36
+
37
+ // Common Options
38
+ if (data.commonOptions && data.commonOptions.length > 0) {
39
+ console.log(chalk.bold('Opciones comunes:'))
40
+ data.commonOptions.forEach(opt => {
41
+ console.log(chalk.cyan(` ${opt.flag}`))
42
+ console.log(chalk.dim(` ${opt.description}`))
43
+ })
44
+ console.log('')
45
+ }
46
+
47
+ // Examples
48
+ if (data.examples && data.examples.length > 0) {
49
+ console.log(chalk.bold('Ejemplos:'))
50
+ data.examples.forEach(ex => {
51
+ console.log(chalk.green(` $ ${ex.command}`))
52
+ console.log(chalk.dim(` ${ex.description}`))
53
+ console.log('')
54
+ })
55
+ }
56
+
57
+ // Tips
58
+ if (data.tips && data.tips.length > 0) {
59
+ console.log(chalk.bold.yellow('💡 Tips:'))
60
+ data.tips.forEach(tip => {
61
+ console.log(chalk.yellow(` • ${tip}`))
62
+ })
63
+ console.log('')
64
+ }
65
+
66
+ // Related Lesson
67
+ if (data.relatedLesson) {
68
+ console.log(chalk.dim(`📚 Lección relacionada: ${data.relatedLesson}`))
69
+ console.log(chalk.dim(` Ejecuta: creta sintaxis`))
70
+ console.log('')
71
+ }
72
+ }
73
+
74
+ showNotFound(commandKey) {
75
+ console.log('')
76
+ console.log(chalk.red(`❌ No tenemos ayuda para: ${chalk.bold(commandKey)}`))
77
+ console.log('')
78
+ console.log(chalk.dim('Comandos disponibles:'))
79
+ console.log('')
80
+
81
+ console.log(chalk.bold(' Básicos:'))
82
+ console.log(chalk.cyan(' ls, cd, mkdir, touch, wc'))
83
+ console.log('')
84
+
85
+ console.log(chalk.bold(' Git:'))
86
+ console.log(chalk.cyan(' git status, git add, git commit, git push, git log'))
87
+ console.log('')
88
+
89
+ console.log(chalk.dim('💡 Ejemplo: creta ls'))
90
+ console.log(chalk.dim('💡 Ejemplo: creta git status'))
91
+ console.log('')
92
+ }
93
+ }
@@ -0,0 +1,121 @@
1
+ import fs from 'fs'
2
+ import path from 'path'
3
+ import os from 'os'
4
+
5
+ export class AccessControl {
6
+ constructor() {
7
+ this.userFilePath = path.join(os.homedir(), '.creta', 'user.json')
8
+ }
9
+
10
+ getUserData() {
11
+ try {
12
+ if (!fs.existsSync(this.userFilePath)) {
13
+ return this.getDefaultUser()
14
+ }
15
+ const data = fs.readFileSync(this.userFilePath, 'utf8')
16
+ return JSON.parse(data)
17
+ } catch (error) {
18
+ return this.getDefaultUser()
19
+ }
20
+ }
21
+
22
+ getDefaultUser() {
23
+ return {
24
+ name: 'guest',
25
+ lessonsCompleted: [],
26
+ projectsCreated: [],
27
+ papersCompleted: []
28
+ }
29
+ }
30
+
31
+ // Paper unlock requirements
32
+ getPaperRequirements() {
33
+ return {
34
+ 'mapreduce': {
35
+ name: 'MapReduce (2004)',
36
+ description: 'Procesamiento de datos en paralelo',
37
+ requirement: 'Completa 3+ lecciones de sintaxis',
38
+ check: (user) => {
39
+ const sintaxisLessons = user.lessonsCompleted.filter(l =>
40
+ l.id && l.id.includes('sintaxis')
41
+ ).length
42
+ return sintaxisLessons >= 3
43
+ }
44
+ },
45
+ 'bitcoin': {
46
+ name: 'Bitcoin (2008)',
47
+ description: 'Blockchain y consenso descentralizado',
48
+ requirement: 'Completa MapReduce + 1 proyecto',
49
+ check: (user) => {
50
+ const hasMapReduce = user.papersCompleted?.some(p => p.id === 'mapreduce')
51
+ const hasProject = user.projectsCreated.length >= 1
52
+ return hasMapReduce && hasProject
53
+ }
54
+ },
55
+ 'spark': {
56
+ name: 'Spark (2010)',
57
+ description: 'MapReduce en memoria (RDDs)',
58
+ requirement: 'Completa MapReduce + Bitcoin',
59
+ check: (user) => {
60
+ const hasMapReduce = user.papersCompleted?.some(p => p.id === 'mapreduce')
61
+ const hasBitcoin = user.papersCompleted?.some(p => p.id === 'bitcoin')
62
+ return hasMapReduce && hasBitcoin
63
+ }
64
+ }
65
+ }
66
+ }
67
+
68
+ isUnlocked(paperId) {
69
+ const user = this.getUserData()
70
+ const requirements = this.getPaperRequirements()
71
+ const paper = requirements[paperId]
72
+
73
+ if (!paper) return false
74
+
75
+ return paper.check(user)
76
+ }
77
+
78
+ getAvailablePapers() {
79
+ const user = this.getUserData()
80
+ const requirements = this.getPaperRequirements()
81
+
82
+ return Object.keys(requirements).map(paperId => ({
83
+ id: paperId,
84
+ ...requirements[paperId],
85
+ unlocked: requirements[paperId].check(user),
86
+ completed: user.papersCompleted?.some(p => p.id === paperId) || false
87
+ }))
88
+ }
89
+
90
+ markPaperCompleted(paperId) {
91
+ const user = this.getUserData()
92
+
93
+ // Initialize papersCompleted if it doesn't exist
94
+ if (!user.papersCompleted) {
95
+ user.papersCompleted = []
96
+ }
97
+
98
+ // Check if already completed
99
+ const alreadyCompleted = user.papersCompleted.some(p => p.id === paperId)
100
+ if (alreadyCompleted) {
101
+ return
102
+ }
103
+
104
+ // Add completion record
105
+ user.papersCompleted.push({
106
+ id: paperId,
107
+ completedAt: new Date().toISOString()
108
+ })
109
+
110
+ // Save to file
111
+ try {
112
+ const dir = path.dirname(this.userFilePath)
113
+ if (!fs.existsSync(dir)) {
114
+ fs.mkdirSync(dir, { recursive: true })
115
+ }
116
+ fs.writeFileSync(this.userFilePath, JSON.stringify(user, null, 2))
117
+ } catch (error) {
118
+ console.error('Error saving user progress:', error.message)
119
+ }
120
+ }
121
+ }
@@ -0,0 +1,276 @@
1
+ import { createInterface } from 'readline'
2
+ import { spawnSync } from 'child_process'
3
+ import chalk from 'chalk'
4
+ import path from 'path'
5
+ import { fileURLToPath } from 'url'
6
+ import { AccessControl } from './AccessControl.js'
7
+
8
+ const __filename = fileURLToPath(import.meta.url)
9
+ const __dirname = path.dirname(__filename)
10
+
11
+ export class PapersExecutor {
12
+ constructor() {
13
+ this.accessControl = new AccessControl()
14
+ }
15
+
16
+ async execute() {
17
+ // Try interactive mode first
18
+ try {
19
+ await this.showInteractiveMenu()
20
+ } catch (error) {
21
+ // Fallback to numbered selection
22
+ await this.showFallbackMenu()
23
+ }
24
+ }
25
+
26
+ async showInteractiveMenu() {
27
+ // Check if setRawMode is available
28
+ if (typeof process.stdin.setRawMode !== 'function') {
29
+ throw new Error('setRawMode not available')
30
+ }
31
+
32
+ const papers = this.accessControl.getAvailablePapers()
33
+ let selectedIndex = 0
34
+
35
+ // Enable raw mode
36
+ process.stdin.setRawMode(true)
37
+ process.stdin.resume()
38
+ process.stdin.setEncoding('utf8')
39
+
40
+ const renderMenu = () => {
41
+ process.stdout.write('\x1b[2J')
42
+ process.stdout.write('\x1b[H')
43
+
44
+ console.log(chalk.bold.cyan('\n📄 Papers Clásicos de Computer Science\n'))
45
+ console.log(chalk.dim('Recrea los papers que cambiaron la historia de la tecnología'))
46
+ console.log(chalk.dim('─'.repeat(60)))
47
+ console.log('')
48
+
49
+ papers.forEach((paper, index) => {
50
+ const isSelected = index === selectedIndex
51
+ const prefix = isSelected ? '▶ ' : ' '
52
+
53
+ let statusIcon = ''
54
+ let statusColor = chalk.white
55
+
56
+ if (paper.completed) {
57
+ statusIcon = '✅ '
58
+ statusColor = chalk.green
59
+ } else if (paper.unlocked) {
60
+ statusIcon = '🔓 '
61
+ statusColor = chalk.cyan
62
+ } else {
63
+ statusIcon = '🔒 '
64
+ statusColor = chalk.dim
65
+ }
66
+
67
+ const highlight = isSelected ? statusColor.bold : statusColor
68
+
69
+ console.log(highlight(`${prefix}${statusIcon}${paper.name}`))
70
+ console.log(highlight(` ${paper.description}`))
71
+
72
+ if (!paper.unlocked) {
73
+ console.log(chalk.dim(` Requisito: ${paper.requirement}`))
74
+ }
75
+
76
+ console.log('')
77
+ })
78
+
79
+ console.log(chalk.dim('\n↑↓: Navegar | Enter: Seleccionar | q: Salir'))
80
+ }
81
+
82
+ return new Promise((resolve) => {
83
+ renderMenu()
84
+
85
+ const onKeyPress = async (key) => {
86
+ if (key === 'q' || key === '\x03') {
87
+ process.stdin.setRawMode(false)
88
+ process.stdin.removeListener('data', onKeyPress)
89
+ console.log(chalk.dim('\nHecho con <3 por icarus.mx'))
90
+ resolve()
91
+ return
92
+ }
93
+
94
+ if (key === '\r' || key === '\n') {
95
+ const selectedPaper = papers[selectedIndex]
96
+
97
+ if (!selectedPaper.unlocked) {
98
+ process.stdout.write('\x1b[2J')
99
+ process.stdout.write('\x1b[H')
100
+ console.log(chalk.yellow(`\n🔒 Este paper está bloqueado\n`))
101
+ console.log(chalk.dim(`Requisito: ${selectedPaper.requirement}\n`))
102
+
103
+ const rl = createInterface({
104
+ input: process.stdin,
105
+ output: process.stdout
106
+ })
107
+
108
+ process.stdin.setRawMode(false)
109
+ process.stdin.removeListener('data', onKeyPress)
110
+
111
+ await new Promise(r => {
112
+ rl.question(chalk.dim('Presiona Enter para volver...'), () => {
113
+ rl.close()
114
+ r()
115
+ })
116
+ })
117
+
118
+ process.stdin.setRawMode(true)
119
+ process.stdin.on('data', onKeyPress)
120
+ renderMenu()
121
+ return
122
+ }
123
+
124
+ process.stdin.setRawMode(false)
125
+ process.stdin.removeListener('data', onKeyPress)
126
+
127
+ await this.launchPaper(selectedPaper.id)
128
+ resolve()
129
+ return
130
+ }
131
+
132
+ // Arrow keys
133
+ if (key === '\u001b[A') {
134
+ selectedIndex = selectedIndex > 0 ? selectedIndex - 1 : papers.length - 1
135
+ renderMenu()
136
+ } else if (key === '\u001b[B') {
137
+ selectedIndex = selectedIndex < papers.length - 1 ? selectedIndex + 1 : 0
138
+ renderMenu()
139
+ }
140
+ }
141
+
142
+ process.stdin.on('data', onKeyPress)
143
+ })
144
+ }
145
+
146
+ async showFallbackMenu() {
147
+ const rl = createInterface({
148
+ input: process.stdin,
149
+ output: process.stdout
150
+ })
151
+
152
+ const askQuestion = (question) => {
153
+ return new Promise((resolve) => {
154
+ rl.question(question, resolve)
155
+ })
156
+ }
157
+
158
+ try {
159
+ console.log(chalk.bold.cyan('\n📄 Papers Clásicos de Computer Science\n'))
160
+ console.log(chalk.dim('Recrea los papers que cambiaron la historia de la tecnología'))
161
+ console.log(chalk.dim('─'.repeat(60)))
162
+ console.log('')
163
+
164
+ const papers = this.accessControl.getAvailablePapers()
165
+
166
+ papers.forEach((paper, index) => {
167
+ let statusIcon = paper.completed ? '✅' : (paper.unlocked ? '🔓' : '🔒')
168
+
169
+ console.log(`${index + 1}. ${statusIcon} ${paper.name}`)
170
+ console.log(` ${paper.description}`)
171
+
172
+ if (!paper.unlocked) {
173
+ console.log(chalk.dim(` Requisito: ${paper.requirement}`))
174
+ }
175
+
176
+ console.log('')
177
+ })
178
+
179
+ const answer = await askQuestion('Elige un número (1-3) o \'q\' para salir: ')
180
+
181
+ if (answer.toLowerCase() === 'q') {
182
+ console.log(chalk.dim('\nHecho con <3 por icarus.mx'))
183
+ rl.close()
184
+ return
185
+ }
186
+
187
+ const selectedIndex = parseInt(answer) - 1
188
+
189
+ if (selectedIndex >= 0 && selectedIndex < papers.length) {
190
+ const selectedPaper = papers[selectedIndex]
191
+
192
+ if (!selectedPaper.unlocked) {
193
+ console.log(chalk.yellow(`\n🔒 Este paper está bloqueado\n`))
194
+ console.log(chalk.dim(`Requisito: ${selectedPaper.requirement}\n`))
195
+ rl.close()
196
+ return
197
+ }
198
+
199
+ rl.close()
200
+ await this.launchPaper(selectedPaper.id)
201
+ } else {
202
+ console.log(chalk.red('❌ Opción inválida'))
203
+ rl.close()
204
+ }
205
+
206
+ } catch (error) {
207
+ console.error('Error:', error.message)
208
+ rl.close()
209
+ }
210
+ }
211
+
212
+ async launchPaper(paperId) {
213
+ process.stdout.write('\x1b[2J')
214
+ process.stdout.write('\x1b[H')
215
+
216
+ // Get paper markdown path
217
+ const paperPath = path.join(__dirname, paperId, `${paperId}.md`)
218
+
219
+ // Show intro message
220
+ const papers = this.accessControl.getPaperRequirements()
221
+ const paper = papers[paperId]
222
+
223
+ console.log(chalk.bold.cyan(`\n📄 ${paper.name}`))
224
+ console.log(chalk.dim(paper.description))
225
+ console.log(chalk.dim('─'.repeat(60)))
226
+ console.log('')
227
+ console.log(chalk.yellow('💡 Vim Tips para empezar:'))
228
+ console.log(chalk.white(' zM - Cierra todos los folds (empieza aquí)'))
229
+ console.log(chalk.white(' za - Abre/cierra fold bajo el cursor'))
230
+ console.log(chalk.white(' } - Salta al siguiente párrafo'))
231
+ console.log(chalk.white(' ]] - Salta a la siguiente sección'))
232
+ console.log('')
233
+ console.log(chalk.green('Presiona Enter para abrir en Neovim...'))
234
+ console.log(chalk.dim('(El paper se marcará completo al cerrar Neovim)'))
235
+
236
+ const rl = createInterface({
237
+ input: process.stdin,
238
+ output: process.stdout
239
+ })
240
+
241
+ await new Promise(resolve => {
242
+ rl.question('', () => {
243
+ rl.close()
244
+ resolve()
245
+ })
246
+ })
247
+
248
+ // Launch nvim
249
+ console.log(chalk.dim('\nAbriendo Neovim...\n'))
250
+
251
+ const result = spawnSync('nvim', ['+zM', paperPath], {
252
+ stdio: 'inherit',
253
+ shell: true
254
+ })
255
+
256
+ // Mark as completed after closing nvim
257
+ if (result.status === 0 || result.status === null) {
258
+ this.accessControl.markPaperCompleted(paperId)
259
+
260
+ console.log('')
261
+ console.log(chalk.bold.green('🎉 ¡Paper completado!'))
262
+ console.log(chalk.dim('Tu progreso ha sido guardado en ~/.creta/user.json'))
263
+ console.log('')
264
+
265
+ // Check what's unlocked now
266
+ const availablePapers = this.accessControl.getAvailablePapers()
267
+ const nextPaper = availablePapers.find(p => !p.completed && p.unlocked)
268
+
269
+ if (nextPaper) {
270
+ console.log(chalk.cyan(`✨ Desbloqueaste: ${nextPaper.name}`))
271
+ console.log(chalk.dim(` ${nextPaper.description}`))
272
+ console.log('')
273
+ }
274
+ }
275
+ }
276
+ }
@@ -66,6 +66,7 @@ export class UserState {
66
66
 
67
67
  static setName(name) {
68
68
  const state = loadState()
69
+ if (!state) return null
69
70
  state.name = name
70
71
  saveState(state)
71
72
  return state
@@ -73,6 +74,18 @@ export class UserState {
73
74
 
74
75
  static updateLastSeen() {
75
76
  const state = loadState()
77
+ if (!state) {
78
+ console.error('Warning: Could not load user state')
79
+ return {
80
+ name: null,
81
+ createdAt: new Date().toISOString(),
82
+ lessonsCompleted: [],
83
+ projectsCreated: [],
84
+ discord: null,
85
+ stats: { totalSessions: 0, lastSeen: null },
86
+ lastSeen: null
87
+ }
88
+ }
76
89
  const now = new Date().toISOString()
77
90
  state.lastSeen = now
78
91
  state.stats = state.stats || { totalSessions: 0, lastSeen: null }
@@ -84,6 +97,7 @@ export class UserState {
84
97
 
85
98
  static addCompletedLesson(lessonId) {
86
99
  const state = loadState()
100
+ if (!state) return null
87
101
  const alreadyCompleted = state.lessonsCompleted.some(
88
102
  lesson => typeof lesson === 'object' ? lesson.id === lessonId : lesson === lessonId
89
103
  )
@@ -100,6 +114,7 @@ export class UserState {
100
114
 
101
115
  static addProject(projectName) {
102
116
  const state = loadState()
117
+ if (!state) return null
103
118
  state.projectsCreated = state.projectsCreated || []
104
119
  state.projectsCreated.push({
105
120
  name: projectName,
package/package.json CHANGED
@@ -1,14 +1,11 @@
1
1
  {
2
2
  "name": "@icarusmx/creta",
3
- "version": "1.4.19",
3
+ "version": "1.5.3",
4
4
  "description": "Salgamos de este laberinto.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "creta": "./bin/creta.js"
8
8
  },
9
- "scripts": {
10
- "test": "node --test"
11
- },
12
9
  "keywords": [
13
10
  "cli",
14
11
  "icarus",