@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.
@@ -3,7 +3,9 @@
3
3
  "allow": [
4
4
  "Read(//private/tmp/**)",
5
5
  "Bash(node:*)",
6
- "WebFetch(domain:github.com)"
6
+ "WebFetch(domain:github.com)",
7
+ "Bash(npm install)",
8
+ "Bash(npm run dev:*)"
7
9
  ],
8
10
  "deny": [],
9
11
  "ask": []
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("¡Bienvenido de vuelta! 🏛️")
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.png" alt="Logo" class="w-8 h-8">
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@icarusmx/creta",
3
- "version": "0.4.3",
3
+ "version": "0.5.1",
4
4
  "description": "Salgamos de este laberinto.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -12,6 +12,7 @@
12
12
  "@sveltejs/kit": "^2.0.0",
13
13
  "@sveltejs/vite-plugin-svelte": "^4.0.0",
14
14
  "svelte": "^5.0.0",
15
+ "tailwindcss": "^4.0.0",
15
16
  "@tailwindcss/vite": "^4.0.0",
16
17
  "vite": "^5.0.3"
17
18
  },
@@ -1,6 +1,4 @@
1
- @tailwind base;
2
- @tailwind components;
3
- @tailwind utilities;
1
+ @import "tailwindcss";
4
2
 
5
3
  /* Animaciones personalizadas */
6
4
  @keyframes fade-in {
@@ -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.png" alt="Logo" class="w-8 h-8">
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: [sveltekit(), tailwindcss()]
6
+ plugins: [
7
+ tailwindcss(),
8
+ sveltekit()
9
+ ]
7
10
  });