@icarusmx/creta 1.4.19 → 1.5.1
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/README.md +395 -0
- package/bin/creta.js +1645 -25
- package/docs/diagrams/README.md +131 -0
- package/docs/diagrams/architecture-overview.mmd +71 -0
- package/docs/diagrams/architecture.svg +1 -0
- package/docs/diagrams/ecosystem-integration.mmd +49 -0
- package/docs/diagrams/evolution-phases.mmd +49 -0
- package/docs/diagrams/output.svg +1 -0
- package/docs/diagrams/phase2-command-help-flow.mmd +51 -0
- package/docs/diagrams/user-journey.mmd +78 -0
- package/lib/data/command-help/cd.js +47 -0
- package/lib/data/command-help/git-add.js +43 -0
- package/lib/data/command-help/git-commit.js +39 -0
- package/lib/data/command-help/git-log.js +47 -0
- package/lib/data/command-help/git-push.js +35 -0
- package/lib/data/command-help/git-status.js +31 -0
- package/lib/data/command-help/index.js +30 -0
- package/lib/data/command-help/ls.js +51 -0
- package/lib/data/command-help/mkdir.js +31 -0
- package/lib/data/command-help/touch.js +26 -0
- package/lib/data/command-help/wc.js +43 -0
- package/lib/data/messages.js +16 -4
- package/lib/executors/CommandHelpExecutor.js +93 -0
- package/lib/papers/AccessControl.js +121 -0
- package/lib/papers/PapersExecutor.js +276 -0
- package/package.json +1 -4
|
@@ -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
|
+
}
|
package/lib/data/messages.js
CHANGED
|
@@ -11,17 +11,29 @@ Salgamos de este laberinto 🏛️
|
|
|
11
11
|
|
|
12
12
|
export const HELP_TEXT = `
|
|
13
13
|
📚 Comandos disponibles:
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
24
|
-
|
|
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
|
+
}
|
package/package.json
CHANGED
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@icarusmx/creta",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.1",
|
|
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",
|