specleap-framework 2.1.8 → 2.1.10

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/CHANGELOG.md CHANGED
@@ -7,6 +7,47 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [2.1.10] - 2026-04-30
11
+
12
+ ### Added
13
+
14
+ - **Installer now also asks for the folder name**, on top of the base folder prompt added in v2.1.9. The user can keep `specleap-framework` (the default) or pick a custom name like `mi-app-tienda` straight from the prompt — no more renaming after install with `mv`.
15
+ - **Two-argument CLI form:** `npx specleap-framework@latest <base> [<name>]`. Both positional. If `<name>` is omitted, the prompt asks. If `<base>` is also omitted, both prompts run interactively.
16
+ - **Folder name validation:** allows letters, numbers, `-`, `_` and `.`, must not start with `.`, must be ≤100 chars. Invalid input on the prompt re-asks until valid; invalid input on the CLI argument aborts with a clear bilingual error and exit 1.
17
+
18
+ ### Changed
19
+
20
+ - The "ya existe" abort message now reads "elige otra carpeta o nombre" (instead of just "elige otra carpeta base"), matching the new flexibility.
21
+
22
+ ### Notes
23
+
24
+ - Backwards compatible with v2.1.9: same env var (`SPECLEAP_INSTALL_PATH`) handed off to `setup.sh`, same final summary block, same fallback behaviour for direct `bash setup.sh` invocations.
25
+ - Decision rationale: keep the prompt-based flow (no native GUI dialog) so the installer keeps working over SSH, in CI/CD pipelines and inside containers — exactly the kind of environments where a Mac-only `osascript` dialog would have broken things.
26
+
27
+ ## [2.1.9] - 2026-04-30
28
+
29
+ ### Added
30
+
31
+ - **Interactive base-folder prompt during `npx specleap-framework@latest`.** The installer now asks where to install before copying anything. Previously it dropped the `specleap-framework/` folder silently into the user's current working directory, leaving them unsure of the final location.
32
+
33
+ #### How it works
34
+
35
+ - `bin/specleap` (the entry point invoked by `npx`) now accepts an optional CLI argument with the base folder, e.g. `npx specleap-framework@latest ~/Downloads`. If the argument is omitted, an interactive bilingual prompt asks for the base folder, with the current working directory as default.
36
+ - The folder name is **always** `specleap-framework` (not configurable). If the user wants a different name, they rename it after install with `mv`.
37
+ - The base path supports `~`, `~/relative`, plain relative paths, and absolute paths. The resolved path is shown back to the user before copying.
38
+ - If the resolved target already exists, the installer aborts with a clear bilingual message instead of silently overwriting.
39
+ - The installer creates the base folder if it does not exist.
40
+
41
+ ### Changed
42
+
43
+ - **Final summary of `setup.sh` now prints the absolute install path prominently.** Block at the end of the installer reads `📍 SpecLeap instalado en: <ruta>` (or `📍 SpecLeap installed at: <path>` in English). Reported by Styng during v2.1.8 testing — after a long setup, the path was effectively lost in the scrollback.
44
+ - **Next-steps list now starts with `cd "<install_path>"`** so the user knows how to reach the project regardless of where they ran `npx` from.
45
+
46
+ ### Notes
47
+
48
+ - Backwards compatible: existing scripts that depend on `setup.sh` running from the project root still work — the env var `SPECLEAP_INSTALL_PATH` is set by `bin/specleap` but `setup.sh` falls back to `pwd` if absent.
49
+ - The folder-name decision (always `specleap-framework`) keeps documentation, generated paths and tutorials consistent across users.
50
+
10
51
  ## [2.1.8] - 2026-04-26
11
52
 
12
53
  ### Fixed
package/README.md CHANGED
@@ -20,7 +20,7 @@
20
20
 
21
21
  SpecLeap convierte cualquier IDE con asistente de IA en un entorno de desarrollo spec-first. Combina un contrato de proyecto inmutable, agentes conversacionales especializados, 34 Agent Skills profesionales y un pipeline de validación en tres capas para entregar código consistente y probado sin improvisación.
22
22
 
23
- **Versión actual:** 2.1.8 · **Licencia:** MIT · **Autor:** Styng Arias ([ConceptualCreative](https://conceptualcreative.com))
23
+ **Versión actual:** 2.1.10 · **Licencia:** MIT · **Autor:** Styng Arias ([ConceptualCreative](https://conceptualcreative.com))
24
24
 
25
25
  ---
26
26
 
package/bin/specleap CHANGED
@@ -3,48 +3,201 @@
3
3
  const { spawn, execSync } = require('child_process');
4
4
  const path = require('path');
5
5
  const fs = require('fs');
6
+ const readline = require('readline');
7
+ const os = require('os');
6
8
 
7
- // Directorio del paquete npm (donde están todos los archivos)
9
+ // Nombre por defecto de la carpeta destino. El usuario puede cambiarlo
10
+ // en el prompt; si pulsa Enter, se usa este valor.
11
+ const DEFAULT_FOLDER_NAME = 'specleap-framework';
12
+
13
+ // Validador de nombre de carpeta: caracteres seguros para sistemas de
14
+ // archivos cross-platform (sin `/`, sin nulos, sin barras). Permite letras,
15
+ // números, guiones, guiones bajos y puntos. No puede empezar con punto.
16
+ function isValidFolderName(name) {
17
+ if (!name || name.length === 0) return false;
18
+ if (name.length > 100) return false;
19
+ if (name.startsWith('.')) return false;
20
+ return /^[A-Za-z0-9_][A-Za-z0-9_.\-]*$/.test(name);
21
+ }
22
+
23
+ // Directorio del paquete npm (donde están todos los archivos a copiar)
8
24
  const packageDir = path.join(__dirname, '..');
9
25
 
10
- // Directorio donde el usuario ejecutó npx (su directorio de trabajo)
11
- const userCwd = process.cwd();
26
+ // Resolver una ruta base introducida por el usuario:
27
+ // - "~" o "~/algo" → expande a $HOME
28
+ // - relativa → se resuelve contra cwd
29
+ // - absoluta → se queda igual
30
+ function resolveBasePath(input) {
31
+ const trimmed = input.trim();
32
+ if (!trimmed) return process.cwd();
33
+ if (trimmed === '~') return os.homedir();
34
+ if (trimmed.startsWith('~/')) return path.join(os.homedir(), trimmed.slice(2));
35
+ if (path.isAbsolute(trimmed)) return trimmed;
36
+ return path.resolve(process.cwd(), trimmed);
37
+ }
12
38
 
13
- // Carpeta de destino
14
- const targetDir = path.join(userCwd, 'specleap-framework');
39
+ // Crea una interfaz readline reutilizable; cerramos al final del flujo.
40
+ function createPrompt() {
41
+ return readline.createInterface({
42
+ input: process.stdin,
43
+ output: process.stdout
44
+ });
45
+ }
15
46
 
16
- console.log('\n🚀 Copiando SpecLeap Framework...\n');
47
+ function ask(rl, question) {
48
+ return new Promise((resolve) => rl.question(question, (a) => resolve(a)));
49
+ }
17
50
 
18
- // Verificar si ya existe
19
- if (fs.existsSync(targetDir)) {
20
- console.error(`❌ Error: El directorio '${targetDir}' ya existe.`);
21
- console.error(' Por favor elimínalo primero o ejecuta desde otro directorio.\n');
22
- process.exit(1);
51
+ // Pregunta la carpeta base donde poner el proyecto. Bilingüe porque el
52
+ // idioma de SpecLeap se elige después dentro de setup.sh.
53
+ async function askBasePath(rl, defaultBase) {
54
+ console.log('');
55
+ console.log('📁 ¿En qué carpeta base instalar SpecLeap?');
56
+ console.log(' Where do you want to install SpecLeap?');
57
+ console.log('');
58
+ console.log(` Default: ${defaultBase}`);
59
+ console.log('');
60
+ console.log(' Ejemplos / Examples:');
61
+ console.log(' ~/Downloads → ~/Downloads/<nombre>');
62
+ console.log(' ~/Desktop/work → ~/Desktop/work/<nombre>');
63
+ console.log(' . → ./<nombre>');
64
+ console.log(' (Enter) → default arriba');
65
+ console.log('');
66
+
67
+ const answer = await ask(rl, 'Carpeta base / Base folder: ');
68
+ return resolveBasePath(answer);
23
69
  }
24
70
 
25
- // Crear directorio destino
26
- try {
27
- fs.mkdirSync(targetDir, { recursive: true });
28
-
29
- // Copiar todos los archivos del paquete
30
- execSync(`cp -R "${packageDir}"/* "${targetDir}"/`, { stdio: 'inherit' });
31
- execSync(`cp -R "${packageDir}"/.??* "${targetDir}"/ 2>/dev/null || true`, { stdio: 'ignore' });
32
-
33
- console.log(`✅ Archivos copiados a: ${targetDir}\n`);
34
-
35
- } catch (err) {
36
- console.error('❌ Error copiando archivos:', err.message);
37
- process.exit(1);
71
+ // Pregunta el nombre de la carpeta. Default = "specleap-framework".
72
+ // Reintenta si el nombre tiene caracteres inválidos.
73
+ async function askFolderName(rl) {
74
+ console.log('');
75
+ console.log('📝 ¿Qué nombre quieres para la carpeta?');
76
+ console.log(' What name do you want for the folder?');
77
+ console.log('');
78
+ console.log(` Default: ${DEFAULT_FOLDER_NAME}`);
79
+ console.log(' Permitido / Allowed: letras, números, guiones, guiones bajos, puntos');
80
+ console.log(' No permitido / Not allowed: espacios, "/", caracteres especiales');
81
+ console.log('');
82
+
83
+ while (true) {
84
+ const answer = await ask(rl, 'Nombre / Name: ');
85
+ const name = answer.trim();
86
+
87
+ // Enter → default
88
+ if (!name) return DEFAULT_FOLDER_NAME;
89
+
90
+ if (isValidFolderName(name)) return name;
91
+
92
+ console.log('');
93
+ console.log(`❌ Nombre inválido / Invalid name: "${name}"`);
94
+ console.log(' Usa solo letras, números, "-", "_" y "." (sin empezar con ".")');
95
+ console.log(' Use only letters, numbers, "-", "_" and "." (cannot start with ".")');
96
+ console.log('');
97
+ }
38
98
  }
39
99
 
40
- // Ejecutar setup.sh DESDE el directorio copiado
41
- const setupPath = path.join(targetDir, 'setup.sh');
100
+ async function main() {
101
+ // Argumentos CLI opcionales:
102
+ // npx specleap-framework@latest <carpeta-base> [<nombre>]
103
+ // p.ej. npx specleap-framework@latest ~/Downloads
104
+ // npx specleap-framework@latest ~/Downloads mi-app
105
+ const args = process.argv.slice(2).filter((a) => !a.startsWith('-'));
106
+ const argBase = args[0];
107
+ const argName = args[1];
42
108
 
43
- const setup = spawn('bash', [setupPath], {
44
- stdio: 'inherit',
45
- cwd: targetDir
46
- });
109
+ // Validar el nombre del argumento CLI antes de seguir
110
+ if (argName && !isValidFolderName(argName)) {
111
+ console.error(`\n❌ Nombre inválido / Invalid name: "${argName}"`);
112
+ console.error(' Usa solo letras, números, "-", "_" y "." (sin empezar con ".")');
113
+ console.error(' Use only letters, numbers, "-", "_" and "." (cannot start with ".")\n');
114
+ process.exit(1);
115
+ }
47
116
 
48
- setup.on('close', (code) => {
49
- process.exit(code);
117
+ // Si hay argumentos CLI, no abrimos prompt; si faltan, preguntamos
118
+ let basePath, folderName;
119
+ const rl = (!argBase || !argName) ? createPrompt() : null;
120
+
121
+ try {
122
+ if (argBase) {
123
+ basePath = resolveBasePath(argBase);
124
+ console.log(`\n📁 Carpeta base: ${basePath}`);
125
+ } else {
126
+ basePath = await askBasePath(rl, process.cwd());
127
+ }
128
+
129
+ if (argName) {
130
+ folderName = argName;
131
+ console.log(`📝 Nombre de carpeta: ${folderName}`);
132
+ } else {
133
+ folderName = await askFolderName(rl);
134
+ }
135
+ } finally {
136
+ if (rl) rl.close();
137
+ }
138
+
139
+ // Verificar que la carpeta base existe (o se puede crear)
140
+ if (!fs.existsSync(basePath)) {
141
+ try {
142
+ fs.mkdirSync(basePath, { recursive: true });
143
+ console.log(`📂 Carpeta base creada / Base folder created: ${basePath}`);
144
+ } catch (err) {
145
+ console.error(`\n❌ No se pudo crear la carpeta base / Could not create base folder: ${basePath}`);
146
+ console.error(` ${err.message}\n`);
147
+ process.exit(1);
148
+ }
149
+ }
150
+
151
+ const baseStat = fs.statSync(basePath);
152
+ if (!baseStat.isDirectory()) {
153
+ console.error(`\n❌ La ruta no es una carpeta / Path is not a directory: ${basePath}\n`);
154
+ process.exit(1);
155
+ }
156
+
157
+ // Destino final: <basePath>/<folderName>
158
+ const targetDir = path.join(basePath, folderName);
159
+
160
+ // No sobrescribir si ya existe
161
+ if (fs.existsSync(targetDir)) {
162
+ console.error(`\n❌ Ya existe / Already exists: ${targetDir}`);
163
+ console.error(' Elige otra carpeta o nombre, o elimina la existente.');
164
+ console.error(' Choose another folder or name, or delete the existing one.\n');
165
+ process.exit(1);
166
+ }
167
+
168
+ console.log(`\n🚀 Copiando SpecLeap Framework a / Copying SpecLeap Framework to:`);
169
+ console.log(` ${targetDir}\n`);
170
+
171
+ // Crear destino y copiar archivos
172
+ try {
173
+ fs.mkdirSync(targetDir, { recursive: true });
174
+ execSync(`cp -R "${packageDir}"/* "${targetDir}"/`, { stdio: 'inherit' });
175
+ execSync(`cp -R "${packageDir}"/.??* "${targetDir}"/ 2>/dev/null || true`, { stdio: 'ignore' });
176
+ console.log(`✅ Archivos copiados / Files copied\n`);
177
+ } catch (err) {
178
+ console.error('❌ Error copiando archivos / Error copying files:', err.message);
179
+ process.exit(1);
180
+ }
181
+
182
+ // Ejecutar setup.sh DESDE el directorio copiado, exportando la ruta
183
+ // para que setup.sh la use en el mensaje final.
184
+ const setupPath = path.join(targetDir, 'setup.sh');
185
+
186
+ const setup = spawn('bash', [setupPath], {
187
+ stdio: 'inherit',
188
+ cwd: targetDir,
189
+ env: {
190
+ ...process.env,
191
+ SPECLEAP_INSTALL_PATH: targetDir
192
+ }
193
+ });
194
+
195
+ setup.on('close', (code) => {
196
+ process.exit(code);
197
+ });
198
+ }
199
+
200
+ main().catch((err) => {
201
+ console.error('\n❌ Error inesperado / Unexpected error:', err.message);
202
+ process.exit(1);
50
203
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "specleap-framework",
3
- "version": "2.1.8",
3
+ "version": "2.1.10",
4
4
  "description": "Spec-Driven Development Framework — Transform VSCode, Cursor, JetBrains into spec-first development machines",
5
5
  "keywords": [
6
6
  "spec-driven-development",
package/setup.sh CHANGED
@@ -516,43 +516,63 @@ echo " ✅ $MSG_SUCCESS"
516
516
  echo -e "${NC}${GREEN}${LINE}${NC}"
517
517
  echo ""
518
518
 
519
+ # Ruta de instalación: viene de bin/specleap como env var SPECLEAP_INSTALL_PATH.
520
+ # Fallback a $PWD si alguien ejecutó setup.sh directamente sin pasar por bin.
521
+ INSTALL_PATH="${SPECLEAP_INSTALL_PATH:-$(pwd)}"
522
+
523
+ if [ "$LANG" = "es" ]; then
524
+ echo -e "${CYAN}${BOLD}📍 SpecLeap instalado en:${NC}"
525
+ else
526
+ echo -e "${CYAN}${BOLD}📍 SpecLeap installed at:${NC}"
527
+ fi
528
+ echo -e " ${BOLD}${GREEN}$INSTALL_PATH${NC}"
529
+ echo ""
530
+ echo -e "${CYAN}${LINE}${NC}"
531
+ echo ""
532
+
519
533
  echo -e "${YELLOW}${BOLD}$MSG_NEXT_STEPS${NC}"
520
534
  echo ""
521
535
 
522
536
  if [ "$LANG" = "es" ]; then
523
- echo " ${BOLD}1. Instalar CodeRabbit en GitHub:${NC}"
537
+ echo " ${BOLD}1. Entra a la carpeta del proyecto:${NC}"
538
+ echo " ${GREEN}cd \"$INSTALL_PATH\"${NC}"
539
+ echo ""
540
+ echo " ${BOLD}2. Instalar CodeRabbit en GitHub:${NC}"
524
541
  echo " ${CYAN}https://github.com/apps/coderabbitai${NC}"
525
542
  echo ""
526
- echo " ${BOLD}2. Ejecutar cuestionario (20 min):${NC}"
543
+ echo " ${BOLD}3. Ejecutar cuestionario (20 min):${NC}"
527
544
  echo " ${GREEN}./scripts/generate-contrato.sh${NC}"
528
545
  echo ""
529
- echo " ${BOLD}3. Push a GitHub:${NC}"
546
+ echo " ${BOLD}4. Push a GitHub:${NC}"
530
547
  echo " ${GREEN}git init && git add . && git commit -m 'init: SpecLeap setup'${NC}"
531
548
  echo " ${GREEN}git push origin main${NC}"
532
549
  echo ""
533
- echo " ${BOLD}4. Abrir en tu IDE:${NC}"
550
+ echo " ${BOLD}5. Abrir en tu IDE:${NC}"
534
551
  echo " ${GREEN}code .${NC} (VSCode)"
535
552
  echo " ${GREEN}cursor .${NC} (Cursor)"
536
553
  echo ""
537
- echo " ${BOLD}5. En el chat con la IA, escribe:${NC}"
554
+ echo " ${BOLD}6. En el chat con la IA, escribe:${NC}"
538
555
  echo " ${GREEN}ayuda${NC}"
539
556
  echo ""
540
557
  else
541
- echo " ${BOLD}1. Install CodeRabbit on GitHub:${NC}"
558
+ echo " ${BOLD}1. Enter the project folder:${NC}"
559
+ echo " ${GREEN}cd \"$INSTALL_PATH\"${NC}"
560
+ echo ""
561
+ echo " ${BOLD}2. Install CodeRabbit on GitHub:${NC}"
542
562
  echo " ${CYAN}https://github.com/apps/coderabbitai${NC}"
543
563
  echo ""
544
- echo " ${BOLD}2. Run questionnaire (20 min):${NC}"
564
+ echo " ${BOLD}3. Run questionnaire (20 min):${NC}"
545
565
  echo " ${GREEN}./scripts/generate-contrato.sh${NC}"
546
566
  echo ""
547
- echo " ${BOLD}3. Push to GitHub:${NC}"
567
+ echo " ${BOLD}4. Push to GitHub:${NC}"
548
568
  echo " ${GREEN}git init && git add . && git commit -m 'init: SpecLeap setup'${NC}"
549
569
  echo " ${GREEN}git push origin main${NC}"
550
570
  echo ""
551
- echo " ${BOLD}4. Open in your IDE:${NC}"
571
+ echo " ${BOLD}5. Open in your IDE:${NC}"
552
572
  echo " ${GREEN}code .${NC} (VSCode)"
553
573
  echo " ${GREEN}cursor .${NC} (Cursor)"
554
574
  echo ""
555
- echo " ${BOLD}5. In AI chat, type:${NC}"
575
+ echo " ${BOLD}6. In AI chat, type:${NC}"
556
576
  echo " ${GREEN}ayuda${NC}"
557
577
  echo ""
558
578
  fi