openprompt-lang 0.3.0 → 0.5.0

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.
Files changed (100) hide show
  1. package/README.md +553 -32
  2. package/bin/cli.js +343 -74
  3. package/bin/create.js +135 -0
  4. package/bin/lint.js +21 -22
  5. package/docs/AI-MANUAL.md +578 -0
  6. package/docs/COMMANDS.md +200 -127
  7. package/docs/COMMITS/INDEX.md +1 -0
  8. package/docs/PROMPT_AI_CONTEXT.md +99 -0
  9. package/docs/ROADMAP-DESARROLLO.md +274 -0
  10. package/docs/langs/dotnet.md +36 -0
  11. package/docs/langs/java-spring.md +45 -0
  12. package/docs/langs/python-fastapi.md +35 -0
  13. package/docs/langs/unity.md +30 -0
  14. package/docs/langs/vue-nuxt.md +36 -0
  15. package/package.json +33 -4
  16. package/scaffolds/.cursorrules +6 -0
  17. package/scaffolds/AGENTS.md +27 -0
  18. package/scaffolds/Dockerfile +11 -0
  19. package/scaffolds/capacitor.config.ts +17 -0
  20. package/scaffolds/netlify.toml +8 -0
  21. package/scaffolds/prompt-lang.json +15 -0
  22. package/scaffolds/railway.json +12 -0
  23. package/scaffolds/tailwind.config.js +8 -0
  24. package/scaffolds/tauri.conf.json +26 -0
  25. package/schemas/language-module.json +116 -0
  26. package/schemas/prompt-lang.json +38 -3
  27. package/schemas/structures.json +145 -0
  28. package/src/ai/learn-prompt.js +90 -0
  29. package/src/ai/prompt-builder.js +182 -0
  30. package/src/ai/providers.js +247 -0
  31. package/src/annotations/registry.js +39 -0
  32. package/src/annotations/tags.json +24 -0
  33. package/src/commands/ai-gen.js +161 -0
  34. package/src/commands/component.js +315 -227
  35. package/src/commands/context.js +351 -153
  36. package/src/commands/db-rules.js +117 -0
  37. package/src/commands/extract.js +562 -0
  38. package/src/commands/figma.js +42 -42
  39. package/src/commands/init.js +763 -246
  40. package/src/commands/integrate.js +544 -0
  41. package/src/commands/lang.js +148 -0
  42. package/src/commands/learn.js +182 -0
  43. package/src/commands/qa-gen.js +139 -0
  44. package/src/commands/scaffold.js +127 -0
  45. package/src/commands/suggest.js +37 -15
  46. package/src/commands/teach.js +109 -0
  47. package/src/commands/validate.js +189 -89
  48. package/src/commands/wizard.js +458 -0
  49. package/src/generators/figma-prompt.js +25 -17
  50. package/src/knowledge/db-rules.json +162 -0
  51. package/src/language-service/plugin.cjs +94 -0
  52. package/src/language-service/plugin.d.ts +6 -0
  53. package/src/mcp-server.js +710 -0
  54. package/src/templates/langs/react/INDEX.json +262 -0
  55. package/src/templates/langs/react/MODULE.json +166 -0
  56. package/src/templates/langs/react/templates/hooks/useAuth.template.ts +134 -0
  57. package/src/templates/langs/react/templates/hooks/useDebounce.template.ts +45 -0
  58. package/src/templates/langs/react/templates/hooks/useForm.template.ts +146 -0
  59. package/src/templates/langs/react/templates/hooks/usePagination.template.ts +108 -0
  60. package/src/templates/langs/react/templates/services/apiService.template.ts +123 -0
  61. package/src/templates/langs/react/templates/ui/Button.template.tsx +87 -0
  62. package/src/templates/langs/react/templates/ui/Card.template.tsx +85 -0
  63. package/src/templates/langs/react/templates/ui/DataTable.template.tsx +163 -0
  64. package/src/templates/langs/react/templates/ui/Input.template.tsx +96 -0
  65. package/src/templates/langs/react/templates/ui/Modal.template.tsx +133 -0
  66. package/src/templates/langs/react/templates/ui/Select.template.tsx +99 -0
  67. package/src/templates/langs/vue/INDEX.json +246 -0
  68. package/src/templates/langs/vue/MODULE.json +105 -0
  69. package/src/templates/langs/vue/templates/composables/useAuth.template.ts +106 -0
  70. package/src/templates/langs/vue/templates/composables/useDebounce.template.ts +47 -0
  71. package/src/templates/langs/vue/templates/composables/useFetch.template.ts +54 -0
  72. package/src/templates/langs/vue/templates/composables/useForm.template.ts +127 -0
  73. package/src/templates/langs/vue/templates/composables/usePagination.template.ts +98 -0
  74. package/src/templates/langs/vue/templates/services/apiService.template.ts +116 -0
  75. package/src/templates/langs/vue/templates/ui/Button.template.vue +79 -0
  76. package/src/templates/langs/vue/templates/ui/Card.template.vue +73 -0
  77. package/src/templates/langs/vue/templates/ui/DataTable.template.vue +115 -0
  78. package/src/templates/langs/vue/templates/ui/Input.template.vue +70 -0
  79. package/src/templates/langs/vue/templates/ui/Modal.template.vue +112 -0
  80. package/src/templates/langs/vue/templates/ui/Select.template.vue +77 -0
  81. package/src/templates/scripts/log-actividad.sh +32 -0
  82. package/src/templates/scripts/log-commit.sh +35 -0
  83. package/src/templates/scripts/log-error.sh +45 -0
  84. package/src/templates/scripts/validate.sh +23 -0
  85. package/src/templates/semantic-index.json +84 -0
  86. package/src/ts-transformer/index.cjs +86 -0
  87. package/src/utils/ai.js +66 -77
  88. package/src/utils/annotations.js +297 -251
  89. package/src/utils/config.js +85 -34
  90. package/src/utils/doc-ingest.js +101 -0
  91. package/src/utils/error-learner.js +205 -0
  92. package/src/utils/file-utils.js +119 -0
  93. package/src/utils/language-loader.js +167 -0
  94. package/src/utils/module-generator.js +235 -0
  95. package/src/utils/semantic-index.js +93 -0
  96. package/src/utils/template-utils.js +44 -0
  97. package/src/vite-plugin/index.js +54 -0
  98. package/vscode-extension/package.json +23 -2
  99. package/vscode-extension/snippets/promptlang.json +1 -3
  100. package/vscode-extension/syntaxes/annotations-code.tmGrammar.json +15 -0
package/bin/cli.js CHANGED
@@ -1,110 +1,379 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { Command } from "commander";
4
- import chalk from "chalk";
5
- import { readFileSync, existsSync } from "fs";
6
- import { join, dirname } from "path";
7
- import { fileURLToPath } from "url";
8
- import { createRequire } from "module";
3
+ import { Command } from 'commander'
4
+ import chalk from 'chalk'
5
+ import { readFileSync, existsSync } from 'fs'
6
+ import { join, dirname } from 'path'
7
+ import { fileURLToPath } from 'url'
9
8
 
10
- const __dirname = dirname(fileURLToPath(import.meta.url));
11
- const require = createRequire(import.meta.url);
12
- const pkg = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8"));
9
+ const __dirname = dirname(fileURLToPath(import.meta.url))
10
+ const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'))
13
11
 
14
- const program = new Command();
12
+ const program = new Command()
15
13
 
16
14
  program
17
- .name("openPrompt-Lang")
18
- .description(chalk.cyan("Framework de anotaciones para desarrollo asistido por IA"))
19
- .version(pkg.version);
15
+ .name('openPrompt-Lang')
16
+ .description(chalk.cyan('Framework de anotaciones para desarrollo asistido por IA'))
17
+ .version(pkg.version)
20
18
 
21
19
  program
22
- .command("init")
23
- .description("Inicializar proyecto con estructura PromptLang")
24
- .option("--name <name>", "Nombre del proyecto")
25
- .option("--describe <text>", "Descripción del proyecto para sugerencia IA")
26
- .option("--existing", "Configurar en proyecto existente (solo archivos de configuración)")
20
+ .command('init')
21
+ .description('Inicializar proyecto con estructura PromptLang')
22
+ .option('--name <name>', 'Nombre del proyecto')
23
+ .option('--describe <text>', 'Descripción del proyecto para sugerencia IA')
24
+ .option('--existing', 'Configurar en proyecto existente (solo archivos de configuración)')
25
+ .option('--dry-run', 'Previsualizar sin crear archivos')
26
+ .option('--stack <items>', 'Stack base (separado por comas, ej: react,typescript,vite,tailwind)')
27
+ .option('--preset <name>', 'Usar preset pre-ensamblado (ej: react-supabase-stripe, vue-firebase-netlify)')
28
+ .option('--list-presets', 'Listar presets disponibles y salir')
29
+ .option('--list-stacks', 'Listar stacks disponibles y salir')
30
+ .option('--learn-from <file>', 'Ruta a perfil de aprendizaje (aprendizaje-perfil.json) para heredar patrones')
31
+ .option('--no-interactive', 'Saltar preguntas interactivas y usar defaults')
27
32
  .action(async (options) => {
28
- const { init } = await import("../src/commands/init.js");
29
- await init(options);
30
- });
33
+ if (options.listStacks) {
34
+ const { listStacks } = await import('../src/commands/init.js')
35
+ await listStacks()
36
+ return
37
+ }
38
+ if (options.listPresets) {
39
+ const { listPresets } = await import('../src/commands/init.js')
40
+ await listPresets()
41
+ return
42
+ }
43
+ const { init } = await import('../src/commands/init.js')
44
+ await init(options)
45
+ })
31
46
 
32
47
  program
33
- .command("figma")
34
- .description("Generar prompt para diseño en Figma con convenciones React + Tailwind")
48
+ .command('figma')
49
+ .description('Generar prompt para diseño en Figma con convenciones React + Tailwind')
35
50
  .action(async () => {
36
- const { figma } = await import("../src/commands/figma.js");
37
- await figma();
38
- });
51
+ const { figma } = await import('../src/commands/figma.js')
52
+ await figma()
53
+ })
39
54
 
40
55
  program
41
- .command("context")
42
- .description("Extraer contexto del proyecto para la IA")
43
- .option("--output <file>", "Archivo de salida", "contexto.md")
44
- .option("--scope <module>", "Filtrar por módulo (@scope)")
45
- .option("--no-ai", "No incluir anotaciones PromptLang en el output")
56
+ .command('context')
57
+ .description('Extraer contexto del proyecto para la IA')
58
+ .option('--output <file>', 'Archivo de salida', 'contexto.md')
59
+ .option('--scope <module>', 'Filtrar por módulo (@scope)')
60
+ .option('--no-ai', 'No incluir anotaciones PromptLang en el output')
61
+ .option('--mode <mode>', 'Modo de extracción: raw (solo archivos), index (solo índice), full (ambos)', 'full')
62
+ .option('--query <text>', 'Consulta semántica para sugerencias de componentes/patrones')
63
+ .option('--domain <domain>', 'Filtrar por dominio de negocio (ecommerce, saas, mobile, api, admin, blog)')
64
+ .option('--projects <list>', 'Proyectos vecinos a referenciar (separados por coma)')
65
+ .option('--dir <path>', 'Directorio base del proyecto (default: cwd)')
46
66
  .action(async (options) => {
47
- const { context } = await import("../src/commands/context.js");
48
- await context(options);
49
- });
67
+ const { context } = await import('../src/commands/context.js')
68
+ await context(options)
69
+ })
50
70
 
51
71
  program
52
- .command("validate")
53
- .description("Ejecutar pipeline de validación y verificar anotaciones")
54
- .option("--fix", "Auto-corregir errores cuando sea posible")
72
+ .command('validate')
73
+ .description('Ejecutar pipeline de validación y verificar anotaciones')
74
+ .option('--fix', 'Auto-corregir errores cuando sea posible')
55
75
  .action(async (options) => {
56
- const { validate } = await import("../src/commands/validate.js");
57
- await validate(options);
58
- });
76
+ const { validate } = await import('../src/commands/validate.js')
77
+ await validate(options)
78
+ })
59
79
 
60
80
  program
61
- .command("suggest")
62
- .description("IA sugiere estructura/stack según descripción del proyecto")
63
- .argument("<description>", "Descripción del proyecto")
81
+ .command('suggest')
82
+ .description('IA sugiere estructura/stack según descripción del proyecto')
83
+ .argument('<description>', 'Descripción del proyecto')
64
84
  .action(async (description) => {
65
- const { suggest } = await import("../src/commands/suggest.js");
66
- await suggest(description);
67
- });
85
+ const { suggest } = await import('../src/commands/suggest.js')
86
+ await suggest(description)
87
+ })
68
88
 
69
89
  const component = program
70
- .command("component")
71
- .description("Gestionar biblioteca de componentes reutilizables");
90
+ .command('component')
91
+ .description('Gestionar biblioteca de componentes reutilizables')
72
92
 
73
93
  component
74
- .command("list")
75
- .description("Listar componentes disponibles")
76
- .option("--library <path>", "Ruta a la biblioteca de componentes")
94
+ .command('list')
95
+ .description('Listar componentes disponibles')
96
+ .option('--library <path>', 'Ruta a la biblioteca de componentes')
97
+ .option('--style <style>', 'Filtrar por estilo UI (shadcn, mantine, chakra, headless, mui)')
98
+ .option('--tech <tech>', 'Filtrar por tecnología (react, vue, angular)')
99
+ .option('--validated', 'Mostrar solo componentes validados/aprendidos')
77
100
  .action(async (options) => {
78
- const { componentList } = await import("../src/commands/component.js");
79
- await componentList(options);
80
- });
101
+ const { componentList } = await import('../src/commands/component.js')
102
+ await componentList(options)
103
+ })
81
104
 
82
105
  component
83
- .command("add <name>")
84
- .description("Agregar componente de la biblioteca al proyecto")
85
- .option("--library <path>", "Ruta a la biblioteca de componentes")
106
+ .command('add <name>')
107
+ .description('Agregar componente desde biblioteca o template del módulo de lenguaje')
108
+ .option('--library <path>', 'Ruta a la biblioteca de componentes')
109
+ .option('--template', 'Usar template del módulo de lenguaje (INDEX.json)')
110
+ .option('--lang <lang>', 'Lenguaje del template (default: de prompt-lang.json)')
111
+ .option('--variants <list>', 'Variantes a incluir (separadas por coma)')
112
+ .option('--output <name>', 'Nombre del archivo de salida (sin extensión)')
113
+ .option('--force', 'Sobrescribir archivo existente')
114
+ .option('--save', 'Marcar componente como validado/aprendido')
86
115
  .action(async (name, options) => {
87
- const { componentAdd } = await import("../src/commands/component.js");
88
- await componentAdd(name, options);
89
- });
116
+ const { componentAdd } = await import('../src/commands/component.js')
117
+ await componentAdd(name, options)
118
+ })
90
119
 
91
120
  component
92
- .command("init")
93
- .description("Inicializar una biblioteca de componentes")
94
- .option("--library <path>", "Ruta donde crear la biblioteca")
95
- .option("--name <name>", "Nombre de la biblioteca")
121
+ .command('init')
122
+ .description('Inicializar una biblioteca de componentes')
123
+ .option('--library <path>', 'Ruta donde crear la biblioteca')
124
+ .option('--name <name>', 'Nombre de la biblioteca')
96
125
  .action(async (options) => {
97
- const { componentInit } = await import("../src/commands/component.js");
98
- await componentInit(options);
99
- });
126
+ const { componentInit } = await import('../src/commands/component.js')
127
+ await componentInit(options)
128
+ })
100
129
 
101
130
  component
102
- .command("manifest")
103
- .description("Mostrar el manifiesto de la biblioteca de componentes")
104
- .option("--library <path>", "Ruta a la biblioteca de componentes")
131
+ .command('manifest')
132
+ .description('Mostrar el manifiesto de la biblioteca de componentes')
133
+ .option('--library <path>', 'Ruta a la biblioteca de componentes')
105
134
  .action(async (options) => {
106
- const { componentManifest } = await import("../src/commands/component.js");
107
- await componentManifest(options);
108
- });
135
+ const { componentManifest } = await import('../src/commands/component.js')
136
+ await componentManifest(options)
137
+ })
109
138
 
110
- program.parse(process.argv);
139
+ // ─── Wizard command ──────────────────────────────────────────────────────────
140
+
141
+ program
142
+ .command('wizard')
143
+ .description('Asistente interactivo para configurar proyecto openPrompt-Lang')
144
+ .action(async () => {
145
+ const { wizard } = await import('../src/commands/wizard.js')
146
+ await wizard()
147
+ })
148
+
149
+ // ─── Teach command ───────────────────────────────────────────────────────────
150
+
151
+ program
152
+ .command('teach <templateId>')
153
+ .description('Enseñanza estructurada desde template con @teachMe')
154
+ .option('--lang <lang>', 'Lenguaje del template (default: react)')
155
+ .option('--code', 'Mostrar código completo del template')
156
+ .option('--force', 'Mostrar template aunque no tenga @teachMe')
157
+ .action(async (templateId, options) => {
158
+ const { teach } = await import('../src/commands/teach.js')
159
+ await teach(templateId, options)
160
+ })
161
+
162
+ // ─── Learn command (Phase D.1) ────────────────────────────────────────────────
163
+
164
+ program
165
+ .command('learn')
166
+ .description('Aprender desde documentación: genera módulos de lenguaje usando IA')
167
+ .option('--docs <paths...>', 'Rutas a archivos/directorios de documentación')
168
+ .option('--from <paths...>', 'Proyectos reales para extraer patrones (opcional)')
169
+ .option('--name <name>', 'Nombre del lenguaje (si no se puede detectar automáticamente)')
170
+ .option('--output <dir>', 'Directorio de salida (default: ~/.openprompt/knowledge/langs/<name>)')
171
+ .option('--update <dir>', 'Actualizar módulo existente en lugar de crear nuevo')
172
+ .option('--integrate', 'Copiar el módulo generado a src/templates/langs/ (solo maintainers)')
173
+ .option('--dry-run', 'Analizar y mostrar preview sin escribir archivos')
174
+ .option('--provider <provider>', 'AI provider: openai | anthropic | ollama | mock')
175
+ .action(async (options) => {
176
+ const { learn } = await import('../src/commands/learn.js')
177
+ await learn(options)
178
+ })
179
+
180
+ // ─── QA commands ─────────────────────────────────────────────────────────────
181
+
182
+ program
183
+ .command('qa-gen')
184
+ .description('Generate tests from learned errors (@learn-error)')
185
+ .option('--lang <lang>', 'Language module (default: from INDEX.json)')
186
+ .option('--source <dir>', 'Source directory to scan for @learn-error')
187
+ .option('--output <dir>', 'Output directory for generated tests (default: src/__tests__)')
188
+ .option('--template <id>', 'Generate tests only for a specific template')
189
+ .option('--dry-run', 'Preview without writing files')
190
+ .option('--no-scan', 'Skip scanning source for new @learn-error annotations')
191
+ .action(async (options) => {
192
+ const { qaGen } = await import('../src/commands/qa-gen.js')
193
+ await qaGen(options)
194
+ })
195
+
196
+ program
197
+ .command('qa-learn')
198
+ .description('Parse @learn-error from a file and persist to language module')
199
+ .option('--file <path>', 'Source file to scan for @learn-error annotations')
200
+ .option('--lang <lang>', 'Language module (default: react)')
201
+ .action(async (options) => {
202
+ const { qaLearn } = await import('../src/commands/qa-gen.js')
203
+ await qaLearn(options)
204
+ })
205
+
206
+ // ─── Lang command ────────────────────────────────────────────────────────────
207
+
208
+ const lang = program.command('lang').description('Gestionar módulos de lenguaje plugables')
209
+
210
+ lang
211
+ .command('list')
212
+ .description('Listar lenguajes disponibles')
213
+ .action(async () => {
214
+ const { langList } = await import('../src/commands/lang.js')
215
+ await langList()
216
+ })
217
+
218
+ lang
219
+ .command('index <langId>')
220
+ .description('Mostrar índice de templates de un lenguaje')
221
+ .option('--category <category>', 'Filtrar por categoría')
222
+ .action(async (langId, options) => {
223
+ const { langIndex } = await import('../src/commands/lang.js')
224
+ await langIndex(langId, options)
225
+ })
226
+
227
+ lang
228
+ .command('search <query>')
229
+ .description('Buscar templates en todos los lenguajes')
230
+ .option('--lang <lang>', 'Filtrar por lenguaje')
231
+ .action(async (query, options) => {
232
+ const { langSearch } = await import('../src/commands/lang.js')
233
+ await langSearch(query, options)
234
+ })
235
+
236
+ lang
237
+ .command('errors [langId]')
238
+ .description('Mostrar errores aprendidos')
239
+ .option('--template <id>', 'Filtrar por template ID')
240
+ .action(async (langId, options) => {
241
+ const { langErrors } = await import('../src/commands/lang.js')
242
+ await langErrors(langId || 'react', options)
243
+ })
244
+
245
+ // ─── DB Rules command ────────────────────────────────────────────────────────
246
+
247
+ program
248
+ .command('db-rules')
249
+ .description('Consultar reglas de base de datos, anti-patrones y buenas prácticas')
250
+ .option('--list', 'Listar todas las reglas disponibles')
251
+ .option('--id <id>', 'Ver detalle de una regla específica (ej: db-001)')
252
+ .option('--tag <tag>', 'Filtrar por tag (ej: performance, security, supabase)')
253
+ .option('--category <cat>', 'Filtrar por categoría (ej: rendimiento, arquitectura)')
254
+ .action(async (options) => {
255
+ const { dbRules } = await import('../src/commands/db-rules.js')
256
+ await dbRules(options)
257
+ })
258
+
259
+ // ─── Extract command ─────────────────────────────────────────────────────────
260
+
261
+ program
262
+ .command('extract [sourceDir]')
263
+ .description('Analyze project and extract reusable components as templates')
264
+ .option('--lang <lang>', 'Language module to extract into (default: from prompt-lang.json)')
265
+ .option('--min-reuse <n>', 'Minimum imports to qualify (default: 2)')
266
+ .option('--dry-run', "Analyze only, don't write files")
267
+ .option('--learn', 'Deep project analysis: extract patterns, conventions and ways of working')
268
+ .option('--output <file>', 'Output file for --learn profile (default: aprendizaje-perfil.json)')
269
+ .action(async (sourceDir, options) => {
270
+ if (options.learn) {
271
+ const { extractLearn } = await import('../src/commands/extract.js')
272
+ await extractLearn(sourceDir || 'src', options)
273
+ return
274
+ }
275
+ const { extractTemplates } = await import('../src/commands/extract.js')
276
+ await extractTemplates(sourceDir || 'src', options)
277
+ })
278
+
279
+ program
280
+ .command('analyze [sourceDir]')
281
+ .description('Analyze project structure, deps, and code health')
282
+ .action(async (sourceDir) => {
283
+ const { extractAnalyze } = await import('../src/commands/extract.js')
284
+ await extractAnalyze(sourceDir || 'src', {})
285
+ })
286
+
287
+ // ─── Integrate command ────────────────────────────────────────────────────────
288
+
289
+ program
290
+ .command('integrate')
291
+ .description('Configure Opencode, Cursor, and VS Code for the project')
292
+ .option('--all', 'Setup all integrations without prompting')
293
+ .option('--lang <lang>', 'Language (auto-detects from prompt-lang.json)')
294
+ .option('--no-ask', 'Skip interactive prompts')
295
+ .option('--from <dir>', 'Transferir contexto desde otro proyecto (memoria estructurada)')
296
+ .option('--to <dir>', 'Proyecto destino para la transferencia de contexto')
297
+ .option('--migrate', 'Modo migración: copiar contexto de negocio entre proyectos')
298
+ .action(async (options) => {
299
+ if (options.migrate || options.from) {
300
+ const { migrateContext } = await import('../src/commands/integrate.js')
301
+ await migrateContext(options)
302
+ return
303
+ }
304
+ const { integrate } = await import('../src/commands/integrate.js')
305
+ await integrate(options)
306
+ })
307
+
308
+ // ─── AI commands ─────────────────────────────────────────────────────────────
309
+
310
+ program
311
+ .command('ai-gen')
312
+ .description('Generate components from natural language descriptions')
313
+ .option('--description <text>', 'Description of what to generate')
314
+ .option('--name <name>', 'Component name (auto-suggested if omitted)')
315
+ .option('--lang <lang>', 'Language module (default: react)')
316
+ .option('--profile <profile>', 'senior | mid | junior (default: mid)')
317
+ .option(
318
+ '--provider <provider>',
319
+ 'AI provider: openai | anthropic | ollama | mock (default: auto-detect)'
320
+ )
321
+ .option('--output <dir>', 'Output directory (default: src/components)')
322
+ .option('--force', 'Overwrite existing files without asking')
323
+ .option('--validate', 'Run annotation validation after generation')
324
+ .option('--verbose', 'Show prompt and extended output')
325
+ .option('--temperature <num>', 'Temperature for generation (default: 0.3)')
326
+ .option('--max-tokens <num>', 'Max tokens for generation (default: 4096)')
327
+ .action(async (options) => {
328
+ const { aiGen } = await import('../src/commands/ai-gen.js')
329
+ await aiGen(options)
330
+ })
331
+
332
+ program
333
+ .command('ai-providers')
334
+ .description('List available AI providers and their status')
335
+ .action(async () => {
336
+ const { aiProviders } = await import('../src/commands/ai-gen.js')
337
+ await aiProviders()
338
+ })
339
+
340
+ // ─── Scaffold command ────────────────────────────────────────────────────────
341
+
342
+ const scaffold = program
343
+ .command('scaffold')
344
+ .description('Gestionar scafffolding de proyectos por framework')
345
+
346
+ scaffold
347
+ .command('folders <framework>')
348
+ .description('Crear estructura de carpetas según framework')
349
+ .option('--name <name>', 'Nombre del proyecto (directorio)')
350
+ .option('--force', 'Sobrescribir archivos existentes')
351
+ .action(async (framework, options) => {
352
+ const { scaffoldFolders } = await import('../src/commands/scaffold.js')
353
+ await scaffoldFolders(framework, options)
354
+ })
355
+
356
+ scaffold
357
+ .command('list')
358
+ .description('Listar frameworks disponibles en el catálogo')
359
+ .action(async () => {
360
+ const { scaffoldList } = await import('../src/commands/scaffold.js')
361
+ await scaffoldList()
362
+ })
363
+
364
+ // ─── MCP server command ───────────────────────────────────────────────────────
365
+
366
+ program
367
+ .command('mcp')
368
+ .description('Start MCP server for opencode integration via stdio')
369
+ .action(async () => {
370
+ const { startServer } = await import('../src/mcp-server.js')
371
+ await startServer()
372
+ })
373
+
374
+ try {
375
+ await program.parseAsync(process.argv)
376
+ } catch (err) {
377
+ console.error(chalk.red(`✘ Error: ${err.message}`))
378
+ process.exitCode = 1
379
+ }
package/bin/create.js ADDED
@@ -0,0 +1,135 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { execSync } from 'child_process'
4
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, copyFileSync, rmSync } from 'fs'
5
+ import { join, dirname } from 'path'
6
+ import { fileURLToPath } from 'url'
7
+ import chalk from 'chalk'
8
+
9
+ const __dirname = dirname(fileURLToPath(import.meta.url))
10
+
11
+ async function main () {
12
+ const args = process.argv.slice(2)
13
+ const projectName = args[0] || 'mi-app'
14
+ const depsIndex = args.indexOf('--deps')
15
+ const deps = depsIndex !== -1 ? args[depsIndex + 1]?.split(',') || [] : []
16
+ const deployIndex = args.indexOf('--deploy')
17
+ const deploy = deployIndex !== -1 ? args[deployIndex + 1] : null
18
+ const hasIonic = args.includes('--ionic')
19
+ const hasTauri = args.includes('--tauri')
20
+ const force = args.includes('--force')
21
+
22
+ const projectDir = join(process.cwd(), projectName)
23
+
24
+ console.log(chalk.cyan(`\n🚀 Creando proyecto: ${projectName}\n`))
25
+
26
+ // 1. Crear con Vite
27
+ try {
28
+ console.log(chalk.blue(' [1/5] Creando base Vite + React + TS...'))
29
+ execSync(
30
+ `npx create-vite@latest ${projectName} --template react-ts ${force ? '--force' : ''}`,
31
+ {
32
+ stdio: 'pipe',
33
+ cwd: process.cwd()
34
+ }
35
+ )
36
+ console.log(chalk.green(' ✅ Base Vite creada'))
37
+ } catch {
38
+ console.log(chalk.yellow(' ⚠️ Vite base ya existe o falló. Continuando...'))
39
+ }
40
+
41
+ // 2. Limpiar boilerplate predecible
42
+ console.log(chalk.blue(' [2/5] Limpiando boilerplate innecesario...'))
43
+ const toDelete = ['src/App.css', 'src/assets', 'public/vite.svg']
44
+ for (const file of toDelete) {
45
+ const fullPath = join(projectDir, file)
46
+ if (existsSync(fullPath)) {
47
+ rmSync(fullPath, { recursive: true, force: true })
48
+ }
49
+ }
50
+ // Reescribir App.tsx minimal
51
+ const appContent = `function App() {
52
+ return <div className="min-h-screen bg-gray-50" />
53
+ }
54
+
55
+ export default App
56
+ `
57
+ writeFileSync(join(projectDir, 'src/App.tsx'), appContent, 'utf-8')
58
+
59
+ // 3. Instalar dependencias adicionales
60
+ if (deps.length > 0) {
61
+ console.log(chalk.blue(` [3/5] Instalando dependencias: ${deps.join(', ')}...`))
62
+ try {
63
+ execSync(`npm install ${deps.join(' ')}`, { stdio: 'pipe', cwd: projectDir })
64
+ console.log(chalk.green(' ✅ Dependencias instaladas'))
65
+ } catch {
66
+ console.log(
67
+ chalk.yellow(' ⚠️ Error instalando dependencias. Continúa con npm install manual.')
68
+ )
69
+ }
70
+ }
71
+
72
+ // 4. Copiar scaffolds
73
+ console.log(chalk.blue(' [4/5] Copiando archivos de configuración...'))
74
+ const scaffoldsDir = join(__dirname, '..', 'scaffolds')
75
+
76
+ const scaffoldFiles = [
77
+ ['tailwind.config.js', 'tailwind.config.js'],
78
+ ['prompt-lang.json', 'prompt-lang.json']
79
+ ]
80
+
81
+ if (deploy === 'railway') scaffoldFiles.push(['railway.json', 'railway.json'])
82
+ if (deploy === 'netlify') scaffoldFiles.push(['netlify.toml', 'netlify.toml'])
83
+ if (hasIonic) scaffoldFiles.push(['capacitor.config.ts', 'capacitor.config.ts'])
84
+ if (hasTauri) scaffoldFiles.push(['tauri.conf.json', 'tauri.conf.json'])
85
+
86
+ for (const [src, dest] of scaffoldFiles) {
87
+ const srcPath = join(scaffoldsDir, src)
88
+ const destPath = join(projectDir, dest)
89
+ if (existsSync(srcPath) && (!existsSync(destPath) || force)) {
90
+ copyFileSync(srcPath, destPath)
91
+ console.log(` ✅ ${dest}`)
92
+ }
93
+ }
94
+
95
+ // 5. Configurar package.json con scripts descriptivos
96
+ console.log(chalk.blue(' [5/5] Agregando scripts descriptivos...'))
97
+ const pkgPath = join(projectDir, 'package.json')
98
+ if (existsSync(pkgPath)) {
99
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'))
100
+ const descriptiveScripts = {
101
+ 'dev:start': 'vite',
102
+ 'dev:preview': 'vite preview',
103
+ 'build:prod': 'vite build',
104
+ 'build:analyze': 'vite build --analyze',
105
+ 'lint:check': 'eslint src --ext .ts,.tsx',
106
+ 'lint:fix': 'eslint src --ext .ts,.tsx --fix',
107
+ 'type:check': 'tsc --noEmit',
108
+ 'test:unit': 'vitest run',
109
+ 'test:watch': 'vitest',
110
+ 'test:coverage': 'vitest run --coverage',
111
+ 'validate:all': 'npm run lint:check && npm run type:check && npm run test:unit'
112
+ }
113
+ pkg.scripts = { ...descriptiveScripts, ...pkg.scripts }
114
+ writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n', 'utf-8')
115
+ console.log(chalk.green(' ✅ Scripts descriptivos agregados'))
116
+ }
117
+
118
+ // Ionic plug
119
+ if (hasIonic) {
120
+ console.log(chalk.blue(' 🔌 Integrando Ionic...'))
121
+ try {
122
+ execSync('npx cap init App com.app.app --web-dir=dist', { stdio: 'pipe', cwd: projectDir })
123
+ execSync('npx cap add android', { stdio: 'pipe', cwd: projectDir })
124
+ console.log(chalk.green(' ✅ Ionic/Capacitor configurado'))
125
+ } catch {
126
+ console.log(chalk.yellow(' ⚠️ Error en setup Ionic. Configura manualmente.'))
127
+ }
128
+ }
129
+
130
+ console.log(chalk.cyan(`\n✅ ${projectName} listo!`))
131
+ console.log(chalk.cyan(` cd ${projectName}`))
132
+ console.log(chalk.cyan(' npm run dev:start\n'))
133
+ }
134
+
135
+ main().catch(console.error)
package/bin/lint.js CHANGED
@@ -1,50 +1,49 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { readFileSync, existsSync } from "fs";
4
- import { join } from "path";
5
- import { loadConfig } from "../src/utils/config.js";
6
- import { lintFile } from "../src/utils/annotations.js";
7
- import chalk from "chalk";
3
+ import { readFileSync, existsSync } from 'fs'
4
+ import { join } from 'path'
5
+ import { lintFile } from '../src/utils/annotations.js'
6
+ import chalk from 'chalk'
8
7
 
9
- const targetFile = join(process.cwd(), process.argv[2] || "");
8
+ const targetFile = join(process.cwd(), process.argv[2] || '')
10
9
 
11
10
  if (!targetFile || !existsSync(targetFile)) {
12
- console.log(chalk.red("❌ Archivo no encontrado"));
13
- console.log("Uso: node bin/lint.js <archivo>");
14
- process.exit(1);
11
+ console.log(chalk.red('❌ Archivo no encontrado'))
12
+ console.log('Uso: node bin/lint.js <archivo>')
13
+ process.exit(1)
15
14
  }
16
15
 
17
- const content = readFileSync(targetFile, "utf-8");
18
- const { annotations, errors, warnings } = lintFile(content);
16
+ const content = readFileSync(targetFile, 'utf-8')
17
+ const { annotations, errors, warnings } = lintFile(content)
19
18
 
20
19
  if (annotations.length === 0) {
21
- console.log(chalk.cyan("📭 Sin anotaciones PromptLang en este archivo"));
22
- process.exit(0);
20
+ console.log(chalk.cyan('📭 Sin anotaciones PromptLang en este archivo'))
21
+ process.exit(0)
23
22
  }
24
23
 
25
- console.log(chalk.cyan(`\n🔍 Anotaciones encontradas: ${annotations.length}\n`));
24
+ console.log(chalk.cyan(`\n🔍 Anotaciones encontradas: ${annotations.length}\n`))
26
25
 
27
26
  for (const ann of annotations) {
28
27
  const argsStr = ann.args.length
29
- ? ann.args.map((a) => (a.key ? `${a.key}: ${a.value}` : a.value)).join(", ")
30
- : "";
31
- console.log(` @${ann.name}${argsStr ? `(${argsStr})` : ""}`);
28
+ ? ann.args.map((a) => (a.key ? `${a.key}: ${a.value}` : a.value)).join(', ')
29
+ : ''
30
+ console.log(` @${ann.name}${argsStr ? `(${argsStr})` : ''}`)
32
31
  }
33
32
 
34
33
  if (errors.length > 0) {
35
- console.log(chalk.red(`\n❌ Errores (${errors.length}):\n`));
34
+ console.log(chalk.red(`\n❌ Errores (${errors.length}):\n`))
36
35
  for (const err of errors) {
37
- console.log(chalk.red(` • ${err}`));
36
+ console.log(chalk.red(` • ${err}`))
38
37
  }
39
38
  }
40
39
 
41
40
  if (warnings.length > 0) {
42
- console.log(chalk.yellow(`\n⚠️ Advertencias (${warnings.length}):\n`));
41
+ console.log(chalk.yellow(`\n⚠️ Advertencias (${warnings.length}):\n`))
43
42
  for (const warn of warnings) {
44
- console.log(chalk.yellow(` • ${warn}`));
43
+ console.log(chalk.yellow(` • ${warn}`))
45
44
  }
46
45
  }
47
46
 
48
47
  if (errors.length === 0 && warnings.length === 0) {
49
- console.log(chalk.green("\n✅ Todas las anotaciones válidas\n"));
48
+ console.log(chalk.green('\n✅ Todas las anotaciones válidas\n'))
50
49
  }