@icarusmx/creta 0.4.3 → 0.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/.claude/settings.local.json +3 -1
- package/bin/creta.js +8 -2
- package/lib/providers/base.js +41 -0
- package/lib/providers/groq.js +50 -0
- package/lib/providers/index.js +66 -0
- package/lib/providers/openai.js +50 -0
- package/lib/session.js +437 -0
- package/package.json +1 -1
- package/templates/sveltekit-portfolio/package.json +1 -0
- package/templates/sveltekit-portfolio/src/app.css +1 -3
- package/templates/sveltekit-portfolio/src/routes/+layout.svelte +1 -1
- package/templates/sveltekit-portfolio/static/logo.svg +4 -0
- package/templates/sveltekit-portfolio/vite.config.js +5 -2
package/bin/creta.js
CHANGED
|
@@ -5,11 +5,12 @@ import { execSync } from 'child_process'
|
|
|
5
5
|
import fs from 'fs'
|
|
6
6
|
import path from 'path'
|
|
7
7
|
import { fileURLToPath } from 'url'
|
|
8
|
+
import { CretaCodeSession } from '../lib/session.js'
|
|
8
9
|
|
|
9
10
|
const args = process.argv.slice(2)
|
|
10
11
|
const command = args[0]
|
|
11
12
|
|
|
12
|
-
console.log("¡
|
|
13
|
+
console.log("¡Bienvenida de vuelta! 🏛️")
|
|
13
14
|
console.log("Retomemos desde donde nos quedamos: hagamos tu portafolio.\n")
|
|
14
15
|
|
|
15
16
|
if (!command) {
|
|
@@ -18,6 +19,7 @@ if (!command) {
|
|
|
18
19
|
console.log(" creta portafolio-1 - Desbloquea nivel 1 (navbar) 🔓")
|
|
19
20
|
console.log(" creta portafolio-2 - Desbloquea nivel 2 (navbar + hero) 🔓")
|
|
20
21
|
console.log(" creta portafolio-3 - Desbloquea nivel 3 (solución completa) 🔓")
|
|
22
|
+
console.log(" creta code - Inicia sesión interactiva de programación 🤖")
|
|
21
23
|
console.log("\n💡 Tip: Si estás dentro de un proyecto existente, los comandos")
|
|
22
24
|
console.log(" portafolio-1/2/3 actualizarán tus archivos directamente")
|
|
23
25
|
process.exit(0)
|
|
@@ -32,6 +34,10 @@ if (command.startsWith('portafolio')) {
|
|
|
32
34
|
} else {
|
|
33
35
|
await createPortfolioProject(level)
|
|
34
36
|
}
|
|
37
|
+
} else if (command === 'code') {
|
|
38
|
+
// Start interactive coding session
|
|
39
|
+
const session = new CretaCodeSession()
|
|
40
|
+
await session.start()
|
|
35
41
|
} else {
|
|
36
42
|
console.log(`Comando no reconocido: ${command}`)
|
|
37
43
|
process.exit(1)
|
|
@@ -146,7 +152,7 @@ function applyLayoutModifications(content, level) {
|
|
|
146
152
|
<div class="max-w-7xl mx-auto px-4">
|
|
147
153
|
<div class="flex justify-between items-center py-3">
|
|
148
154
|
<div class="flex items-center space-x-3">
|
|
149
|
-
<img src="/logo.
|
|
155
|
+
<img src="/logo.svg" alt="Logo" class="w-8 h-8">
|
|
150
156
|
<span class="text-xl font-bold text-gray-900">{{STUDENT_NAME}}</span>
|
|
151
157
|
</div>
|
|
152
158
|
<div class="hidden md:flex space-x-6">
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export class BaseLLMProvider {
|
|
2
|
+
constructor(config = {}) {
|
|
3
|
+
this.config = config
|
|
4
|
+
this.name = 'base'
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
async chat(messages, options = {}) {
|
|
8
|
+
throw new Error('chat method must be implemented by provider')
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async isAvailable() {
|
|
12
|
+
return false
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
formatMessages(userInput, context = {}) {
|
|
16
|
+
const systemPrompt = `Eres un mentor de programación que ayuda a estudiantes mexicanos a aprender desarrollo web.
|
|
17
|
+
|
|
18
|
+
CONTEXTO:
|
|
19
|
+
- Eres parte de Creta, una escuela de software mexicana
|
|
20
|
+
- Los estudiantes están aprendiendo SvelteKit 5 + Tailwind CSS 4
|
|
21
|
+
- Usa un tono amigable y alentador en español
|
|
22
|
+
- Enfócate en explicaciones prácticas y ejemplos de código
|
|
23
|
+
- Menciona conceptos de programación cuando sea relevante
|
|
24
|
+
|
|
25
|
+
PROYECTO ACTUAL:
|
|
26
|
+
${context.currentProject ? `- Proyecto: ${context.currentProject.name}` : '- No hay proyecto activo'}
|
|
27
|
+
${context.currentFile ? `- Archivo actual: ${context.currentFile}` : ''}
|
|
28
|
+
|
|
29
|
+
INSTRUCCIONES:
|
|
30
|
+
- Responde en español mexicano
|
|
31
|
+
- Usa emojis ocasionalmente para hacer más amigable la conversación
|
|
32
|
+
- Si es una pregunta técnica, incluye código de ejemplo
|
|
33
|
+
- Si mencionas Tailwind, usa clases actualizadas de la v4
|
|
34
|
+
- Mantén las respuestas concisas pero útiles`
|
|
35
|
+
|
|
36
|
+
return [
|
|
37
|
+
{ role: 'system', content: systemPrompt },
|
|
38
|
+
{ role: 'user', content: userInput }
|
|
39
|
+
]
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import fetch from 'node-fetch'
|
|
2
|
+
import { BaseLLMProvider } from './base.js'
|
|
3
|
+
|
|
4
|
+
export class GroqProvider extends BaseLLMProvider {
|
|
5
|
+
constructor(config = {}) {
|
|
6
|
+
super(config)
|
|
7
|
+
this.name = 'groq'
|
|
8
|
+
this.apiKey = config.apiKey || process.env.GROQ_API_KEY
|
|
9
|
+
this.model = config.model || 'mixtral-8x7b-32768'
|
|
10
|
+
this.baseURL = 'https://api.groq.com/openai/v1'
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async isAvailable() {
|
|
14
|
+
return !!this.apiKey
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async chat(userInput, context = {}) {
|
|
18
|
+
if (!this.apiKey) {
|
|
19
|
+
throw new Error('API key de Groq no configurada. Usa: export GROQ_API_KEY=tu_clave')
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const messages = this.formatMessages(userInput, context)
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
const response = await fetch(`${this.baseURL}/chat/completions`, {
|
|
26
|
+
method: 'POST',
|
|
27
|
+
headers: {
|
|
28
|
+
'Authorization': `Bearer ${this.apiKey}`,
|
|
29
|
+
'Content-Type': 'application/json'
|
|
30
|
+
},
|
|
31
|
+
body: JSON.stringify({
|
|
32
|
+
model: this.model,
|
|
33
|
+
messages,
|
|
34
|
+
max_tokens: 1000,
|
|
35
|
+
temperature: 0.7
|
|
36
|
+
})
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
if (!response.ok) {
|
|
40
|
+
throw new Error(`Groq API error: ${response.status}`)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const data = await response.json()
|
|
44
|
+
return data.choices[0].message.content
|
|
45
|
+
|
|
46
|
+
} catch (error) {
|
|
47
|
+
throw new Error(`Error conectando con Groq: ${error.message}`)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { OpenAIProvider } from './openai.js'
|
|
2
|
+
import { GroqProvider } from './groq.js'
|
|
3
|
+
|
|
4
|
+
export class LLMManager {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.providers = new Map()
|
|
7
|
+
this.currentProvider = null
|
|
8
|
+
|
|
9
|
+
// Register available providers
|
|
10
|
+
this.registerProvider('openai', OpenAIProvider)
|
|
11
|
+
this.registerProvider('groq', GroqProvider)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
registerProvider(name, ProviderClass) {
|
|
15
|
+
this.providers.set(name, ProviderClass)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async getAvailableProviders() {
|
|
19
|
+
const available = []
|
|
20
|
+
|
|
21
|
+
for (const [name, ProviderClass] of this.providers) {
|
|
22
|
+
const provider = new ProviderClass()
|
|
23
|
+
if (await provider.isAvailable()) {
|
|
24
|
+
available.push(name)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return available
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async selectProvider(preferredProvider = null) {
|
|
32
|
+
const available = await this.getAvailableProviders()
|
|
33
|
+
|
|
34
|
+
if (available.length === 0) {
|
|
35
|
+
throw new Error('No hay proveedores de IA disponibles. Configura una API key:\n' +
|
|
36
|
+
'- OpenAI: export OPENAI_API_KEY=tu_clave\n' +
|
|
37
|
+
'- Groq: export GROQ_API_KEY=tu_clave')
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Use preferred provider if available
|
|
41
|
+
if (preferredProvider && available.includes(preferredProvider)) {
|
|
42
|
+
const ProviderClass = this.providers.get(preferredProvider)
|
|
43
|
+
this.currentProvider = new ProviderClass()
|
|
44
|
+
return preferredProvider
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Otherwise use first available
|
|
48
|
+
const selectedName = available[0]
|
|
49
|
+
const ProviderClass = this.providers.get(selectedName)
|
|
50
|
+
this.currentProvider = new ProviderClass()
|
|
51
|
+
|
|
52
|
+
return selectedName
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async chat(userInput, context = {}) {
|
|
56
|
+
if (!this.currentProvider) {
|
|
57
|
+
await this.selectProvider()
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return await this.currentProvider.chat(userInput, context)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
getCurrentProviderName() {
|
|
64
|
+
return this.currentProvider?.name || 'ninguno'
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import fetch from 'node-fetch'
|
|
2
|
+
import { BaseLLMProvider } from './base.js'
|
|
3
|
+
|
|
4
|
+
export class OpenAIProvider extends BaseLLMProvider {
|
|
5
|
+
constructor(config = {}) {
|
|
6
|
+
super(config)
|
|
7
|
+
this.name = 'openai'
|
|
8
|
+
this.apiKey = config.apiKey || process.env.OPENAI_API_KEY
|
|
9
|
+
this.model = config.model || 'gpt-4o-mini'
|
|
10
|
+
this.baseURL = config.baseURL || 'https://api.openai.com/v1'
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async isAvailable() {
|
|
14
|
+
return !!this.apiKey
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async chat(userInput, context = {}) {
|
|
18
|
+
if (!this.apiKey) {
|
|
19
|
+
throw new Error('API key de OpenAI no configurada. Usa: export OPENAI_API_KEY=tu_clave')
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const messages = this.formatMessages(userInput, context)
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
const response = await fetch(`${this.baseURL}/chat/completions`, {
|
|
26
|
+
method: 'POST',
|
|
27
|
+
headers: {
|
|
28
|
+
'Authorization': `Bearer ${this.apiKey}`,
|
|
29
|
+
'Content-Type': 'application/json'
|
|
30
|
+
},
|
|
31
|
+
body: JSON.stringify({
|
|
32
|
+
model: this.model,
|
|
33
|
+
messages,
|
|
34
|
+
max_tokens: 1000,
|
|
35
|
+
temperature: 0.7
|
|
36
|
+
})
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
if (!response.ok) {
|
|
40
|
+
throw new Error(`OpenAI API error: ${response.status}`)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const data = await response.json()
|
|
44
|
+
return data.choices[0].message.content
|
|
45
|
+
|
|
46
|
+
} catch (error) {
|
|
47
|
+
throw new Error(`Error conectando con OpenAI: ${error.message}`)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
package/lib/session.js
ADDED
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
import { createInterface } from 'readline'
|
|
2
|
+
import chalk from 'chalk'
|
|
3
|
+
import chokidar from 'chokidar'
|
|
4
|
+
import fs from 'fs'
|
|
5
|
+
import path from 'path'
|
|
6
|
+
import { LLMManager } from './providers/index.js'
|
|
7
|
+
|
|
8
|
+
export class CretaCodeSession {
|
|
9
|
+
constructor() {
|
|
10
|
+
this.rl = null
|
|
11
|
+
this.watcher = null
|
|
12
|
+
this.currentProject = null
|
|
13
|
+
this.isActive = false
|
|
14
|
+
this.llm = new LLMManager()
|
|
15
|
+
this.context = {
|
|
16
|
+
files: new Map(),
|
|
17
|
+
lastModified: new Map()
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async start() {
|
|
22
|
+
console.log(chalk.cyan('🏛️ Creta Code - Sesión Interactiva'))
|
|
23
|
+
console.log(chalk.gray('Escribe "ayuda" para ver comandos disponibles\n'))
|
|
24
|
+
|
|
25
|
+
// Initialize project context
|
|
26
|
+
await this.initializeContext()
|
|
27
|
+
|
|
28
|
+
// Initialize LLM provider
|
|
29
|
+
await this.initializeLLM()
|
|
30
|
+
|
|
31
|
+
// Setup file watcher
|
|
32
|
+
this.setupFileWatcher()
|
|
33
|
+
|
|
34
|
+
// Setup readline interface
|
|
35
|
+
this.rl = createInterface({
|
|
36
|
+
input: process.stdin,
|
|
37
|
+
output: process.stdout,
|
|
38
|
+
prompt: chalk.blue('creta> ')
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
this.isActive = true
|
|
42
|
+
this.rl.prompt()
|
|
43
|
+
|
|
44
|
+
this.rl.on('line', async (input) => {
|
|
45
|
+
await this.handleCommand(input.trim())
|
|
46
|
+
if (this.isActive) {
|
|
47
|
+
this.rl.prompt()
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
this.rl.on('close', () => {
|
|
52
|
+
this.cleanup()
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async initializeContext() {
|
|
57
|
+
const cwd = process.cwd()
|
|
58
|
+
|
|
59
|
+
// Check if we're in a Creta project
|
|
60
|
+
if (this.isInCretaProject(cwd)) {
|
|
61
|
+
this.currentProject = {
|
|
62
|
+
path: cwd,
|
|
63
|
+
type: 'creta-portfolio',
|
|
64
|
+
name: this.extractProjectName(cwd)
|
|
65
|
+
}
|
|
66
|
+
console.log(chalk.green(`📁 Proyecto detectado: ${this.currentProject.name}`))
|
|
67
|
+
} else {
|
|
68
|
+
console.log(chalk.yellow('⚠️ No se detectó un proyecto Creta. Algunas funciones podrían estar limitadas.'))
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async initializeLLM() {
|
|
73
|
+
try {
|
|
74
|
+
const provider = await this.llm.selectProvider()
|
|
75
|
+
console.log(chalk.green(`🤖 IA activada: ${provider}`))
|
|
76
|
+
} catch (error) {
|
|
77
|
+
console.log(chalk.yellow('⚠️ IA no disponible: ' + error.message))
|
|
78
|
+
console.log(chalk.gray(' Algunas funciones estarán limitadas\n'))
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
setupFileWatcher() {
|
|
83
|
+
if (!this.currentProject) return
|
|
84
|
+
|
|
85
|
+
const watchPaths = [
|
|
86
|
+
path.join(this.currentProject.path, 'src/**/*.svelte'),
|
|
87
|
+
path.join(this.currentProject.path, 'src/**/*.js'),
|
|
88
|
+
path.join(this.currentProject.path, 'src/**/*.css')
|
|
89
|
+
]
|
|
90
|
+
|
|
91
|
+
this.watcher = chokidar.watch(watchPaths, {
|
|
92
|
+
ignored: /node_modules/,
|
|
93
|
+
persistent: true
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
this.watcher.on('change', (filePath) => {
|
|
97
|
+
const relativePath = path.relative(this.currentProject.path, filePath)
|
|
98
|
+
console.log(chalk.gray(`\n📝 Archivo modificado: ${relativePath}`))
|
|
99
|
+
this.rl.prompt()
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async handleCommand(input) {
|
|
104
|
+
const [command, ...args] = input.split(' ')
|
|
105
|
+
|
|
106
|
+
switch (command.toLowerCase()) {
|
|
107
|
+
case 'ayuda':
|
|
108
|
+
case 'help':
|
|
109
|
+
this.showHelp()
|
|
110
|
+
break
|
|
111
|
+
|
|
112
|
+
case 'explica':
|
|
113
|
+
await this.explainCode(args.join(' '))
|
|
114
|
+
break
|
|
115
|
+
|
|
116
|
+
case 'revisa':
|
|
117
|
+
await this.reviewCode(args.join(' '))
|
|
118
|
+
break
|
|
119
|
+
|
|
120
|
+
case 'depura':
|
|
121
|
+
await this.debugCode(args.join(' '))
|
|
122
|
+
break
|
|
123
|
+
|
|
124
|
+
case 'nivel':
|
|
125
|
+
await this.checkLevel()
|
|
126
|
+
break
|
|
127
|
+
|
|
128
|
+
case 'archivo':
|
|
129
|
+
await this.showFileContent(args[0])
|
|
130
|
+
break
|
|
131
|
+
|
|
132
|
+
case 'estado':
|
|
133
|
+
this.showProjectStatus()
|
|
134
|
+
break
|
|
135
|
+
|
|
136
|
+
case 'salir':
|
|
137
|
+
case 'exit':
|
|
138
|
+
console.log(chalk.cyan('\n¡Hasta la vista! 👋'))
|
|
139
|
+
this.isActive = false
|
|
140
|
+
this.rl.close()
|
|
141
|
+
break
|
|
142
|
+
|
|
143
|
+
default:
|
|
144
|
+
if (input) {
|
|
145
|
+
console.log(chalk.red(`Comando no reconocido: "${command}"`))
|
|
146
|
+
console.log(chalk.gray('Escribe "ayuda" para ver comandos disponibles'))
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
showHelp() {
|
|
152
|
+
console.log(chalk.cyan('\n🎯 Comandos disponibles:'))
|
|
153
|
+
console.log(' ' + chalk.blue('explica [componente]') + ' - Explica un archivo o concepto')
|
|
154
|
+
console.log(' ' + chalk.blue('revisa [archivo]') + ' - Revisa tu código y da sugerencias')
|
|
155
|
+
console.log(' ' + chalk.blue('depura [error]') + ' - Ayuda a resolver errores')
|
|
156
|
+
console.log(' ' + chalk.blue('nivel') + ' - Verifica tu progreso actual')
|
|
157
|
+
console.log(' ' + chalk.blue('archivo [nombre]') + ' - Muestra el contenido de un archivo')
|
|
158
|
+
console.log(' ' + chalk.blue('estado') + ' - Estado del proyecto actual')
|
|
159
|
+
console.log(' ' + chalk.blue('ayuda') + ' - Muestra esta ayuda')
|
|
160
|
+
console.log(' ' + chalk.blue('salir') + ' - Termina la sesión')
|
|
161
|
+
console.log('')
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async explainCode(target) {
|
|
165
|
+
if (!target) {
|
|
166
|
+
console.log(chalk.yellow('💡 Uso: explica [archivo o concepto]'))
|
|
167
|
+
console.log(chalk.gray('Ejemplo: explica +layout.svelte'))
|
|
168
|
+
return
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
console.log(chalk.cyan(`🔍 Explicando: ${target}`))
|
|
172
|
+
|
|
173
|
+
try {
|
|
174
|
+
let context = {
|
|
175
|
+
currentProject: this.currentProject,
|
|
176
|
+
currentFile: target
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// If it's a file, try to read its content
|
|
180
|
+
const filePath = this.findFile(target)
|
|
181
|
+
if (filePath && fs.existsSync(filePath)) {
|
|
182
|
+
const content = fs.readFileSync(filePath, 'utf8')
|
|
183
|
+
context.fileContent = content.slice(0, 2000) // Limit content for API
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const prompt = filePath
|
|
187
|
+
? `Explica este archivo: ${target}\n\nContenido:\n${context.fileContent || 'No se pudo leer el archivo'}`
|
|
188
|
+
: `Explica este concepto de programación web: ${target}`
|
|
189
|
+
|
|
190
|
+
const response = await this.llm.chat(prompt, context)
|
|
191
|
+
console.log(chalk.green('\n🤖 Explicación:'))
|
|
192
|
+
console.log(response)
|
|
193
|
+
|
|
194
|
+
} catch (error) {
|
|
195
|
+
console.log(chalk.red(`❌ Error: ${error.message}`))
|
|
196
|
+
|
|
197
|
+
// Fallback explanation
|
|
198
|
+
if (target.includes('.svelte')) {
|
|
199
|
+
console.log(chalk.green('\n📚 Explicación básica:'))
|
|
200
|
+
console.log('Este es un componente Svelte que define la estructura de tu página.')
|
|
201
|
+
console.log('- Los archivos +layout.svelte definen el layout común (navbar, footer)')
|
|
202
|
+
console.log('- Los archivos +page.svelte definen el contenido específico de cada página')
|
|
203
|
+
console.log('- Tailwind CSS se usa para el styling con clases como "bg-blue-600"')
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async reviewCode(fileName) {
|
|
209
|
+
if (!this.currentProject) {
|
|
210
|
+
console.log(chalk.red('❌ No hay proyecto activo para revisar'))
|
|
211
|
+
return
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (!fileName) {
|
|
215
|
+
console.log(chalk.yellow('💡 Uso: revisa [archivo]'))
|
|
216
|
+
console.log(chalk.gray('Ejemplo: revisa +layout.svelte'))
|
|
217
|
+
return
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const filePath = this.findFile(fileName)
|
|
221
|
+
if (!filePath) {
|
|
222
|
+
console.log(chalk.red(`❌ No se encontró el archivo: ${fileName}`))
|
|
223
|
+
return
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
console.log(chalk.cyan(`🔍 Revisando: ${fileName}`))
|
|
227
|
+
|
|
228
|
+
try {
|
|
229
|
+
const content = fs.readFileSync(filePath, 'utf8')
|
|
230
|
+
|
|
231
|
+
const context = {
|
|
232
|
+
currentProject: this.currentProject,
|
|
233
|
+
currentFile: fileName,
|
|
234
|
+
fileContent: content.slice(0, 3000)
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const prompt = `Revisa este código y dame sugerencias de mejora. Enfócate en:
|
|
238
|
+
- Buenas prácticas de SvelteKit 5
|
|
239
|
+
- Uso correcto de Tailwind CSS 4
|
|
240
|
+
- Estructura y organización del código
|
|
241
|
+
- Posibles errores o mejoras
|
|
242
|
+
|
|
243
|
+
Archivo: ${fileName}
|
|
244
|
+
|
|
245
|
+
Código:
|
|
246
|
+
${context.fileContent}`
|
|
247
|
+
|
|
248
|
+
const response = await this.llm.chat(prompt, context)
|
|
249
|
+
console.log(chalk.green('\n🤖 Revisión de código:'))
|
|
250
|
+
console.log(response)
|
|
251
|
+
|
|
252
|
+
} catch (error) {
|
|
253
|
+
console.log(chalk.red(`❌ Error: ${error.message}`))
|
|
254
|
+
|
|
255
|
+
// Fallback basic review
|
|
256
|
+
console.log(chalk.green('\n✅ Revisión básica:'))
|
|
257
|
+
|
|
258
|
+
if (content.includes('⚠️')) {
|
|
259
|
+
console.log(chalk.yellow('- Hay comentarios con retos pendientes (⚠️)'))
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (content.includes('TODO') || content.includes('FIXME')) {
|
|
263
|
+
console.log(chalk.yellow('- Encontré comentarios TODO/FIXME'))
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
console.log(chalk.green('- La sintaxis parece correcta'))
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
async debugCode(issue) {
|
|
271
|
+
console.log(chalk.cyan(`🐛 Depurando: ${issue || 'proyecto actual'}`))
|
|
272
|
+
|
|
273
|
+
if (!this.currentProject) {
|
|
274
|
+
console.log(chalk.yellow('💡 Tips generales:'))
|
|
275
|
+
console.log('- Asegúrate de estar en un proyecto Creta')
|
|
276
|
+
console.log('- Ejecuta "npm install" para instalar dependencias')
|
|
277
|
+
console.log('- Usa "npm run dev" para iniciar el servidor')
|
|
278
|
+
return
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
console.log(chalk.green('\n🔧 Verificaciones:'))
|
|
282
|
+
|
|
283
|
+
// Check package.json
|
|
284
|
+
const packagePath = path.join(this.currentProject.path, 'package.json')
|
|
285
|
+
if (fs.existsSync(packagePath)) {
|
|
286
|
+
console.log(chalk.green('✅ package.json existe'))
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Check node_modules
|
|
290
|
+
const nodeModulesPath = path.join(this.currentProject.path, 'node_modules')
|
|
291
|
+
if (fs.existsSync(nodeModulesPath)) {
|
|
292
|
+
console.log(chalk.green('✅ Dependencias instaladas'))
|
|
293
|
+
} else {
|
|
294
|
+
console.log(chalk.yellow('⚠️ Ejecuta: npm install'))
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
console.log(chalk.gray('\n(Próximamente: diagnóstico avanzado con IA)'))
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
async checkLevel() {
|
|
301
|
+
if (!this.currentProject) {
|
|
302
|
+
console.log(chalk.red('❌ No hay proyecto activo'))
|
|
303
|
+
return
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
console.log(chalk.cyan('📊 Verificando progreso...'))
|
|
307
|
+
|
|
308
|
+
const layoutPath = path.join(this.currentProject.path, 'src/routes/+layout.svelte')
|
|
309
|
+
const pagePath = path.join(this.currentProject.path, 'src/routes/+page.svelte')
|
|
310
|
+
|
|
311
|
+
let level = 0
|
|
312
|
+
|
|
313
|
+
if (fs.existsSync(layoutPath)) {
|
|
314
|
+
const layoutContent = fs.readFileSync(layoutPath, 'utf8')
|
|
315
|
+
if (!layoutContent.includes('⚠️ COMPLETA AQUÍ LA NAVEGACIÓN')) {
|
|
316
|
+
level = Math.max(level, 1)
|
|
317
|
+
}
|
|
318
|
+
if (!layoutContent.includes('⚠️ COMPLETA AQUÍ EL FOOTER')) {
|
|
319
|
+
level = Math.max(level, 3)
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (fs.existsSync(pagePath)) {
|
|
324
|
+
const pageContent = fs.readFileSync(pagePath, 'utf8')
|
|
325
|
+
if (!pageContent.includes('⚠️ COMPLETA AQUÍ LA SECCIÓN HERO')) {
|
|
326
|
+
level = Math.max(level, 2)
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
console.log(chalk.green(`\n🎯 Nivel actual: ${level}`))
|
|
331
|
+
if (level === 0) console.log(chalk.gray('Completa el navbar para llegar al nivel 1'))
|
|
332
|
+
if (level === 1) console.log(chalk.gray('Completa la sección hero para llegar al nivel 2'))
|
|
333
|
+
if (level === 2) console.log(chalk.gray('Completa el footer para llegar al nivel 3'))
|
|
334
|
+
if (level === 3) console.log(chalk.green('¡Portafolio completo! 🎉'))
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
async showFileContent(fileName) {
|
|
338
|
+
if (!fileName) {
|
|
339
|
+
console.log(chalk.yellow('💡 Uso: archivo [nombre]'))
|
|
340
|
+
return
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const filePath = this.findFile(fileName)
|
|
344
|
+
if (!filePath) {
|
|
345
|
+
console.log(chalk.red(`❌ No se encontró: ${fileName}`))
|
|
346
|
+
return
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
try {
|
|
350
|
+
const content = fs.readFileSync(filePath, 'utf8')
|
|
351
|
+
const lines = content.split('\n')
|
|
352
|
+
|
|
353
|
+
console.log(chalk.cyan(`\n📄 ${fileName}:`))
|
|
354
|
+
console.log(chalk.gray('─'.repeat(50)))
|
|
355
|
+
|
|
356
|
+
lines.slice(0, 20).forEach((line, i) => {
|
|
357
|
+
console.log(chalk.gray(`${(i + 1).toString().padStart(2)}: `) + line)
|
|
358
|
+
})
|
|
359
|
+
|
|
360
|
+
if (lines.length > 20) {
|
|
361
|
+
console.log(chalk.gray(`... y ${lines.length - 20} líneas más`))
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
} catch (error) {
|
|
365
|
+
console.log(chalk.red(`❌ Error: ${error.message}`))
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
showProjectStatus() {
|
|
370
|
+
console.log(chalk.cyan('\n📊 Estado del Proyecto:'))
|
|
371
|
+
|
|
372
|
+
if (this.currentProject) {
|
|
373
|
+
console.log(chalk.green(`✅ Proyecto: ${this.currentProject.name}`))
|
|
374
|
+
console.log(chalk.green(`📁 Ruta: ${this.currentProject.path}`))
|
|
375
|
+
console.log(chalk.green(`🎯 Tipo: ${this.currentProject.type}`))
|
|
376
|
+
} else {
|
|
377
|
+
console.log(chalk.red('❌ No hay proyecto activo'))
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (this.watcher) {
|
|
381
|
+
console.log(chalk.green('👀 Monitoreo de archivos: activo'))
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
findFile(fileName) {
|
|
386
|
+
if (!this.currentProject) return null
|
|
387
|
+
|
|
388
|
+
const possiblePaths = [
|
|
389
|
+
path.join(this.currentProject.path, fileName),
|
|
390
|
+
path.join(this.currentProject.path, 'src', fileName),
|
|
391
|
+
path.join(this.currentProject.path, 'src/routes', fileName),
|
|
392
|
+
path.join(this.currentProject.path, 'src/lib', fileName)
|
|
393
|
+
]
|
|
394
|
+
|
|
395
|
+
for (const filePath of possiblePaths) {
|
|
396
|
+
if (fs.existsSync(filePath)) {
|
|
397
|
+
return filePath
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
return null
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
isInCretaProject(dir) {
|
|
405
|
+
const packageJsonPath = path.join(dir, 'package.json')
|
|
406
|
+
const layoutPath = path.join(dir, 'src/routes/+layout.svelte')
|
|
407
|
+
|
|
408
|
+
if (!fs.existsSync(packageJsonPath) || !fs.existsSync(layoutPath)) {
|
|
409
|
+
return false
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
try {
|
|
413
|
+
const layoutContent = fs.readFileSync(layoutPath, 'utf8')
|
|
414
|
+
return layoutContent.includes('RETO CRETA') || layoutContent.includes('🧭 RETO')
|
|
415
|
+
} catch {
|
|
416
|
+
return false
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
extractProjectName(projectPath) {
|
|
421
|
+
try {
|
|
422
|
+
const packageJsonPath = path.join(projectPath, 'package.json')
|
|
423
|
+
const packageContent = fs.readFileSync(packageJsonPath, 'utf8')
|
|
424
|
+
const packageJson = JSON.parse(packageContent)
|
|
425
|
+
return packageJson.name || path.basename(projectPath)
|
|
426
|
+
} catch {
|
|
427
|
+
return path.basename(projectPath)
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
cleanup() {
|
|
432
|
+
if (this.watcher) {
|
|
433
|
+
this.watcher.close()
|
|
434
|
+
}
|
|
435
|
+
this.isActive = false
|
|
436
|
+
}
|
|
437
|
+
}
|
package/package.json
CHANGED
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
<div class="max-w-7xl mx-auto px-4">
|
|
50
50
|
<div class="flex justify-between items-center py-3">
|
|
51
51
|
<div class="flex items-center space-x-3">
|
|
52
|
-
<img src="/logo.
|
|
52
|
+
<img src="/logo.svg" alt="Logo" class="w-8 h-8">
|
|
53
53
|
<span class="text-xl font-bold text-gray-900">{{STUDENT_NAME}}</span>
|
|
54
54
|
</div>
|
|
55
55
|
<div class="hidden md:flex space-x-6">
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<circle cx="16" cy="16" r="14" fill="#3b82f6" stroke="#1e40af" stroke-width="2"/>
|
|
3
|
+
<text x="16" y="22" text-anchor="middle" fill="white" font-family="Arial, sans-serif" font-size="18" font-weight="bold">C</text>
|
|
4
|
+
</svg>
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { sveltekit } from '@sveltejs/kit/vite';
|
|
2
|
-
import tailwindcss from '@tailwindcss/vite';
|
|
3
2
|
import { defineConfig } from 'vite';
|
|
3
|
+
import tailwindcss from '@tailwindcss/vite';
|
|
4
4
|
|
|
5
5
|
export default defineConfig({
|
|
6
|
-
plugins: [
|
|
6
|
+
plugins: [
|
|
7
|
+
tailwindcss(),
|
|
8
|
+
sveltekit()
|
|
9
|
+
]
|
|
7
10
|
});
|