specleap-framework 2.1.7 → 2.1.9

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,46 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [2.1.9] - 2026-04-30
11
+
12
+ ### Added
13
+
14
+ - **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.
15
+
16
+ #### How it works
17
+
18
+ - `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.
19
+ - The folder name is **always** `specleap-framework` (not configurable). If the user wants a different name, they rename it after install with `mv`.
20
+ - The base path supports `~`, `~/relative`, plain relative paths, and absolute paths. The resolved path is shown back to the user before copying.
21
+ - If the resolved target already exists, the installer aborts with a clear bilingual message instead of silently overwriting.
22
+ - The installer creates the base folder if it does not exist.
23
+
24
+ ### Changed
25
+
26
+ - **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.
27
+ - **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.
28
+
29
+ ### Notes
30
+
31
+ - 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.
32
+ - The folder-name decision (always `specleap-framework`) keeps documentation, generated paths and tutorials consistent across users.
33
+
34
+ ## [2.1.8] - 2026-04-26
35
+
36
+ ### Fixed
37
+
38
+ - **`setup.sh` token validation no longer aborts on the first wrong input.** Both the GitHub and the Asana token prompts now retry up to 3 times before asking the user whether to retry the round again or cancel the installation. Empty input doesn't consume an attempt (it's almost always a paste/typo, not a real failure). Reported by Styng during a manual test where pasting a partial Asana token killed the installer outright with no recovery path.
39
+
40
+ #### Behavioural details
41
+
42
+ - 3 attempts per round, then the installer prints the actual count and asks `[R]eintentar / [C]ancelar` (Spanish) or `[R]etry / [C]ancel` (English) depending on the chosen language.
43
+ - Choosing `R` (default) resets the attempt counter and lets the user try 3 more times — useful when they realise mid-prompt that they grabbed the wrong token.
44
+ - Choosing `C` exits cleanly with code 1 and the message `Instalación cancelada por el usuario` / `Installation cancelled by user`.
45
+ - After every invalid token, a tip is printed reminding the user about required scopes (GitHub: `repo`, `workflow`) or token format (Asana: Personal Access Token).
46
+ - Empty token input prints a localised reminder of the expected format and re-prompts without spending an attempt.
47
+
48
+ No changes to the validation logic itself (still calls `https://api.github.com/user` and `https://app.asana.com/api/1.0/workspaces`), so behaviour with valid tokens is unchanged.
49
+
10
50
  ## [2.1.7] - 2026-04-25
11
51
 
12
52
  ### Added
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.7 · **Licencia:** MIT · **Autor:** Styng Arias ([ConceptualCreative](https://conceptualcreative.com))
23
+ **Versión actual:** 2.1.9 · **Licencia:** MIT · **Autor:** Styng Arias ([ConceptualCreative](https://conceptualcreative.com))
24
24
 
25
25
  ---
26
26
 
package/bin/specleap CHANGED
@@ -3,48 +3,137 @@
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 fijo de la carpeta destino. NO es configurable: si el usuario
10
+ // quiere otro nombre, renombra la carpeta a posteriori con `mv`.
11
+ const FOLDER_NAME = 'specleap-framework';
12
+
13
+ // Directorio del paquete npm (donde están todos los archivos a copiar)
8
14
  const packageDir = path.join(__dirname, '..');
9
15
 
10
- // Directorio donde el usuario ejecutó npx (su directorio de trabajo)
11
- const userCwd = process.cwd();
16
+ // Resolver una ruta base introducida por el usuario:
17
+ // - "~" o "~/algo" → expande a $HOME
18
+ // - relativa → se resuelve contra cwd
19
+ // - absoluta → se queda igual
20
+ function resolveBasePath(input) {
21
+ const trimmed = input.trim();
22
+ if (!trimmed) return process.cwd();
23
+ if (trimmed === '~') return os.homedir();
24
+ if (trimmed.startsWith('~/')) return path.join(os.homedir(), trimmed.slice(2));
25
+ if (path.isAbsolute(trimmed)) return trimmed;
26
+ return path.resolve(process.cwd(), trimmed);
27
+ }
12
28
 
13
- // Carpeta de destino
14
- const targetDir = path.join(userCwd, 'specleap-framework');
29
+ // Pregunta interactiva al usuario por la carpeta base.
30
+ // Bilingüe en el prompt porque el idioma de SpecLeap se elige más tarde
31
+ // dentro de setup.sh — aquí todavía no lo sabemos.
32
+ function askBasePath(defaultBase) {
33
+ return new Promise((resolve) => {
34
+ const rl = readline.createInterface({
35
+ input: process.stdin,
36
+ output: process.stdout
37
+ });
15
38
 
16
- console.log('\n🚀 Copiando SpecLeap Framework...\n');
39
+ console.log('');
40
+ console.log('📁 ¿En qué carpeta base instalar SpecLeap?');
41
+ console.log(' Where do you want to install SpecLeap?');
42
+ console.log('');
43
+ console.log(` Se creará / Will create: <base>/${FOLDER_NAME}`);
44
+ console.log(` Default: ${defaultBase}`);
45
+ console.log('');
46
+ console.log(' Ejemplos / Examples:');
47
+ console.log(' ~/Downloads → ~/Downloads/specleap-framework');
48
+ console.log(' ~/Desktop/work → ~/Desktop/work/specleap-framework');
49
+ console.log(' . → ./specleap-framework');
50
+ console.log(' (Enter) → default arriba');
51
+ console.log('');
17
52
 
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);
53
+ rl.question('Carpeta base / Base folder: ', (answer) => {
54
+ rl.close();
55
+ resolve(resolveBasePath(answer));
56
+ });
57
+ });
23
58
  }
24
59
 
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);
38
- }
60
+ async function main() {
61
+ // Argumento CLI opcional: `npx specleap-framework@latest ~/Downloads`
62
+ const args = process.argv.slice(2);
63
+ const argBase = args.find((a) => !a.startsWith('-'));
39
64
 
40
- // Ejecutar setup.sh DESDE el directorio copiado
41
- const setupPath = path.join(targetDir, 'setup.sh');
65
+ // Si no hay argumento, preguntar interactivamente con default = cwd
66
+ let basePath;
67
+ if (argBase) {
68
+ basePath = resolveBasePath(argBase);
69
+ console.log(`\n📁 Carpeta base: ${basePath}`);
70
+ } else {
71
+ basePath = await askBasePath(process.cwd());
72
+ }
42
73
 
43
- const setup = spawn('bash', [setupPath], {
44
- stdio: 'inherit',
45
- cwd: targetDir
46
- });
74
+ // Verificar que la carpeta base existe (o se puede crear)
75
+ if (!fs.existsSync(basePath)) {
76
+ try {
77
+ fs.mkdirSync(basePath, { recursive: true });
78
+ console.log(`📂 Carpeta base creada / Base folder created: ${basePath}`);
79
+ } catch (err) {
80
+ console.error(`\n❌ No se pudo crear la carpeta base / Could not create base folder: ${basePath}`);
81
+ console.error(` ${err.message}\n`);
82
+ process.exit(1);
83
+ }
84
+ }
85
+
86
+ // Verificar que sea un directorio (no un archivo con ese nombre)
87
+ const baseStat = fs.statSync(basePath);
88
+ if (!baseStat.isDirectory()) {
89
+ console.error(`\n❌ La ruta no es una carpeta / Path is not a directory: ${basePath}\n`);
90
+ process.exit(1);
91
+ }
92
+
93
+ // Construir el destino final: <basePath>/specleap-framework
94
+ const targetDir = path.join(basePath, FOLDER_NAME);
47
95
 
48
- setup.on('close', (code) => {
49
- process.exit(code);
96
+ // No sobrescribir si ya existe
97
+ if (fs.existsSync(targetDir)) {
98
+ console.error(`\n❌ Ya existe / Already exists: ${targetDir}`);
99
+ console.error(' Elige otra carpeta base o elimina la existente.');
100
+ console.error(' Choose another base folder or delete the existing one.\n');
101
+ process.exit(1);
102
+ }
103
+
104
+ console.log(`\n🚀 Copiando SpecLeap Framework a / Copying SpecLeap Framework to:`);
105
+ console.log(` ${targetDir}\n`);
106
+
107
+ // Crear destino y copiar archivos
108
+ try {
109
+ fs.mkdirSync(targetDir, { recursive: true });
110
+ execSync(`cp -R "${packageDir}"/* "${targetDir}"/`, { stdio: 'inherit' });
111
+ execSync(`cp -R "${packageDir}"/.??* "${targetDir}"/ 2>/dev/null || true`, { stdio: 'ignore' });
112
+ console.log(`✅ Archivos copiados / Files copied\n`);
113
+ } catch (err) {
114
+ console.error('❌ Error copiando archivos / Error copying files:', err.message);
115
+ process.exit(1);
116
+ }
117
+
118
+ // Ejecutar setup.sh DESDE el directorio copiado, exportando la ruta
119
+ // para que setup.sh la use en el mensaje final.
120
+ const setupPath = path.join(targetDir, 'setup.sh');
121
+
122
+ const setup = spawn('bash', [setupPath], {
123
+ stdio: 'inherit',
124
+ cwd: targetDir,
125
+ env: {
126
+ ...process.env,
127
+ SPECLEAP_INSTALL_PATH: targetDir
128
+ }
129
+ });
130
+
131
+ setup.on('close', (code) => {
132
+ process.exit(code);
133
+ });
134
+ }
135
+
136
+ main().catch((err) => {
137
+ console.error('\n❌ Error inesperado / Unexpected error:', err.message);
138
+ process.exit(1);
50
139
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "specleap-framework",
3
- "version": "2.1.7",
3
+ "version": "2.1.9",
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
@@ -167,22 +167,70 @@ else
167
167
  echo ""
168
168
  fi
169
169
 
170
- read -p "$(echo -e ${YELLOW}GitHub Token:${NC} )" GITHUB_TOKEN
171
- echo ""
170
+ # Validación con reintentos: hasta 3 intentos antes de pedir abort/reintentar
171
+ GITHUB_USER=""
172
+ GITHUB_ATTEMPT=0
173
+ GITHUB_MAX_ATTEMPTS=3
172
174
 
173
- if [ -z "$GITHUB_TOKEN" ]; then
174
- echo -e "${RED}$MSG_ERROR GitHub token is required${NC}"
175
- exit 1
176
- fi
175
+ while [ -z "$GITHUB_USER" ]; do
176
+ GITHUB_ATTEMPT=$((GITHUB_ATTEMPT + 1))
177
177
 
178
- # Verificar token
179
- echo -ne "Verificando token... "
180
- GITHUB_USER=$(curl -s -H "Authorization: token $GITHUB_TOKEN" https://api.github.com/user | grep -o '"login": "[^"]*' | cut -d'"' -f4)
178
+ read -p "$(echo -e ${YELLOW}GitHub Token:${NC} )" GITHUB_TOKEN
179
+ echo ""
181
180
 
182
- if [ -z "$GITHUB_USER" ]; then
183
- echo -e "${RED}❌ Token inválido${NC}"
184
- exit 1
185
- fi
181
+ if [ -z "$GITHUB_TOKEN" ]; then
182
+ if [ "$LANG" = "es" ]; then
183
+ echo -e "${RED}❌ Token vacío. Pega tu token de GitHub.${NC}"
184
+ else
185
+ echo -e "${RED}❌ Empty token. Paste your GitHub token.${NC}"
186
+ fi
187
+ echo ""
188
+ # No cuenta como intento si está vacío (probable typo / pegado mal)
189
+ GITHUB_ATTEMPT=$((GITHUB_ATTEMPT - 1))
190
+ continue
191
+ fi
192
+
193
+ # Verificar token contra la API de GitHub
194
+ echo -ne "Verificando token... "
195
+ GITHUB_USER=$(curl -s -H "Authorization: token $GITHUB_TOKEN" https://api.github.com/user | grep -o '"login": "[^"]*' | cut -d'"' -f4)
196
+
197
+ if [ -z "$GITHUB_USER" ]; then
198
+ echo -e "${RED}❌ Token inválido${NC}"
199
+ echo ""
200
+
201
+ if [ "$GITHUB_ATTEMPT" -ge "$GITHUB_MAX_ATTEMPTS" ]; then
202
+ if [ "$LANG" = "es" ]; then
203
+ echo -e "${YELLOW}⚠️ Has fallado $GITHUB_MAX_ATTEMPTS veces.${NC}"
204
+ read -p "$(echo -e ${YELLOW}¿[R]eintentar o [C]ancelar instalación? [R/c]:${NC} )" RETRY_CHOICE
205
+ else
206
+ echo -e "${YELLOW}⚠️ You've failed $GITHUB_MAX_ATTEMPTS times.${NC}"
207
+ read -p "$(echo -e ${YELLOW}[R]etry or [C]ancel installation? [R/c]:${NC} )" RETRY_CHOICE
208
+ fi
209
+
210
+ if [[ "$RETRY_CHOICE" =~ ^[Cc]$ ]]; then
211
+ if [ "$LANG" = "es" ]; then
212
+ echo -e "${RED}Instalación cancelada por el usuario.${NC}"
213
+ else
214
+ echo -e "${RED}Installation cancelled by user.${NC}"
215
+ fi
216
+ exit 1
217
+ fi
218
+ # Reset contador para nueva ronda de 3 intentos
219
+ GITHUB_ATTEMPT=0
220
+ echo ""
221
+ continue
222
+ fi
223
+
224
+ if [ "$LANG" = "es" ]; then
225
+ echo "Intenta de nuevo (intento $((GITHUB_ATTEMPT + 1))/$GITHUB_MAX_ATTEMPTS)."
226
+ echo "Verifica que el token tenga los permisos: repo, workflow."
227
+ else
228
+ echo "Try again (attempt $((GITHUB_ATTEMPT + 1))/$GITHUB_MAX_ATTEMPTS)."
229
+ echo "Make sure the token has the scopes: repo, workflow."
230
+ fi
231
+ echo ""
232
+ fi
233
+ done
186
234
 
187
235
  echo -e "${GREEN}✅ Token válido (Usuario: $GITHUB_USER)${NC}"
188
236
  echo ""
@@ -227,65 +275,113 @@ else
227
275
  echo ""
228
276
  fi
229
277
 
230
- read -p "$(echo -e ${YELLOW}Asana Token:${NC} )" ASANA_TOKEN
231
- echo ""
232
-
233
- if [ -z "$ASANA_TOKEN" ]; then
234
- echo -e "${RED}$MSG_ERROR Asana token is required${NC}"
235
- exit 1
236
- fi
278
+ # Validación con reintentos: hasta 3 intentos antes de pedir abort/reintentar
279
+ ASANA_VALID=""
280
+ ASANA_ATTEMPT=0
281
+ ASANA_MAX_ATTEMPTS=3
237
282
 
238
- # Verificar token y obtener workspace
239
- echo -ne "Verificando token Asana... "
240
- ASANA_RESPONSE=$(curl -s -H "Authorization: Bearer $ASANA_TOKEN" https://app.asana.com/api/1.0/workspaces)
283
+ while [ -z "$ASANA_VALID" ]; do
284
+ ASANA_ATTEMPT=$((ASANA_ATTEMPT + 1))
241
285
 
242
- if echo "$ASANA_RESPONSE" | grep -q '"gid"'; then
243
- echo -e "${GREEN}✅ Token válido${NC}"
286
+ read -p "$(echo -e ${YELLOW}Asana Token:${NC} )" ASANA_TOKEN
244
287
  echo ""
245
-
246
- # Listar workspaces disponibles
247
- if [ "$LANG" = "es" ]; then
248
- echo "Workspaces disponibles:"
249
- else
250
- echo "Available workspaces:"
288
+
289
+ if [ -z "$ASANA_TOKEN" ]; then
290
+ if [ "$LANG" = "es" ]; then
291
+ echo -e "${RED}❌ Token vacío. Pega tu token de Asana (empieza con '0/' o '1/' o '2/').${NC}"
292
+ else
293
+ echo -e "${RED}❌ Empty token. Paste your Asana token (starts with '0/' or '1/' or '2/').${NC}"
294
+ fi
295
+ echo ""
296
+ ASANA_ATTEMPT=$((ASANA_ATTEMPT - 1))
297
+ continue
251
298
  fi
252
- echo ""
253
-
254
- # Extraer workspaces (simple parsing)
255
- WORKSPACE_COUNT=0
256
- while IFS= read -r line; do
257
- if echo "$line" | grep -q '"gid"'; then
258
- WORKSPACE_COUNT=$((WORKSPACE_COUNT + 1))
259
- WS_GID=$(echo "$line" | grep -o '"gid":"[^"]*' | cut -d'"' -f4)
260
- WS_NAME=$(echo "$ASANA_RESPONSE" | grep -A1 "\"gid\":\"$WS_GID\"" | grep '"name"' | grep -o '"name":"[^"]*' | cut -d'"' -f4)
261
- echo " $WORKSPACE_COUNT) $WS_NAME (ID: $WS_GID)"
262
-
263
- # Guardar primer workspace como default
264
- if [ $WORKSPACE_COUNT -eq 1 ]; then
265
- DEFAULT_WORKSPACE_ID="$WS_GID"
299
+
300
+ # Verificar token y obtener workspace
301
+ echo -ne "Verificando token Asana... "
302
+ ASANA_RESPONSE=$(curl -s -H "Authorization: Bearer $ASANA_TOKEN" https://app.asana.com/api/1.0/workspaces)
303
+
304
+ if echo "$ASANA_RESPONSE" | grep -q '"gid"'; then
305
+ echo -e "${GREEN}✅ Token válido${NC}"
306
+ ASANA_VALID="yes"
307
+ else
308
+ echo -e "${RED}❌ Token inválido${NC}"
309
+ echo ""
310
+
311
+ if [ "$ASANA_ATTEMPT" -ge "$ASANA_MAX_ATTEMPTS" ]; then
312
+ if [ "$LANG" = "es" ]; then
313
+ echo -e "${YELLOW}⚠️ Has fallado $ASANA_MAX_ATTEMPTS veces.${NC}"
314
+ read -p "$(echo -e ${YELLOW}¿[R]eintentar o [C]ancelar instalación? [R/c]:${NC} )" RETRY_CHOICE
315
+ else
316
+ echo -e "${YELLOW}⚠️ You've failed $ASANA_MAX_ATTEMPTS times.${NC}"
317
+ read -p "$(echo -e ${YELLOW}[R]etry or [C]ancel installation? [R/c]:${NC} )" RETRY_CHOICE
266
318
  fi
319
+
320
+ if [[ "$RETRY_CHOICE" =~ ^[Cc]$ ]]; then
321
+ if [ "$LANG" = "es" ]; then
322
+ echo -e "${RED}Instalación cancelada por el usuario.${NC}"
323
+ else
324
+ echo -e "${RED}Installation cancelled by user.${NC}"
325
+ fi
326
+ exit 1
327
+ fi
328
+ ASANA_ATTEMPT=0
329
+ echo ""
330
+ continue
267
331
  fi
268
- done < <(echo "$ASANA_RESPONSE" | grep '"gid"')
269
-
270
- echo ""
271
- if [ "$LANG" = "es" ]; then
272
- read -p "$(echo -e ${YELLOW}Workspace ID [Enter para usar el primero]:${NC} )" ASANA_WORKSPACE_INPUT
273
- else
274
- read -p "$(echo -e ${YELLOW}Workspace ID [Enter to use first]:${NC} )" ASANA_WORKSPACE_INPUT
332
+
333
+ if [ "$LANG" = "es" ]; then
334
+ echo "Intenta de nuevo (intento $((ASANA_ATTEMPT + 1))/$ASANA_MAX_ATTEMPTS)."
335
+ echo "Verifica que el token sea Personal Access Token de Asana."
336
+ else
337
+ echo "Try again (attempt $((ASANA_ATTEMPT + 1))/$ASANA_MAX_ATTEMPTS)."
338
+ echo "Make sure the token is an Asana Personal Access Token."
339
+ fi
340
+ echo ""
275
341
  fi
276
-
277
- if [ -z "$ASANA_WORKSPACE_INPUT" ]; then
278
- ASANA_WORKSPACE_ID="$DEFAULT_WORKSPACE_ID"
279
- else
280
- ASANA_WORKSPACE_ID="$ASANA_WORKSPACE_INPUT"
342
+ done
343
+
344
+ echo ""
345
+
346
+ # Listar workspaces disponibles
347
+ if [ "$LANG" = "es" ]; then
348
+ echo "Workspaces disponibles:"
349
+ else
350
+ echo "Available workspaces:"
351
+ fi
352
+ echo ""
353
+
354
+ # Extraer workspaces (simple parsing)
355
+ WORKSPACE_COUNT=0
356
+ while IFS= read -r line; do
357
+ if echo "$line" | grep -q '"gid"'; then
358
+ WORKSPACE_COUNT=$((WORKSPACE_COUNT + 1))
359
+ WS_GID=$(echo "$line" | grep -o '"gid":"[^"]*' | cut -d'"' -f4)
360
+ WS_NAME=$(echo "$ASANA_RESPONSE" | grep -A1 "\"gid\":\"$WS_GID\"" | grep '"name"' | grep -o '"name":"[^"]*' | cut -d'"' -f4)
361
+ echo " $WORKSPACE_COUNT) $WS_NAME (ID: $WS_GID)"
362
+
363
+ # Guardar primer workspace como default
364
+ if [ $WORKSPACE_COUNT -eq 1 ]; then
365
+ DEFAULT_WORKSPACE_ID="$WS_GID"
366
+ fi
281
367
  fi
282
-
283
- echo -e "${GREEN}✅ Workspace seleccionado: $ASANA_WORKSPACE_ID${NC}"
368
+ done < <(echo "$ASANA_RESPONSE" | grep '"gid"')
369
+
370
+ echo ""
371
+ if [ "$LANG" = "es" ]; then
372
+ read -p "$(echo -e ${YELLOW}Workspace ID [Enter para usar el primero]:${NC} )" ASANA_WORKSPACE_INPUT
284
373
  else
285
- echo -e "${RED} Token inválido${NC}"
286
- exit 1
374
+ read -p "$(echo -e ${YELLOW}Workspace ID [Enter to use first]:${NC} )" ASANA_WORKSPACE_INPUT
375
+ fi
376
+
377
+ if [ -z "$ASANA_WORKSPACE_INPUT" ]; then
378
+ ASANA_WORKSPACE_ID="$DEFAULT_WORKSPACE_ID"
379
+ else
380
+ ASANA_WORKSPACE_ID="$ASANA_WORKSPACE_INPUT"
287
381
  fi
288
382
 
383
+ echo -e "${GREEN}✅ Workspace seleccionado: $ASANA_WORKSPACE_ID${NC}"
384
+
289
385
  echo ""
290
386
  sleep 1
291
387
 
@@ -420,43 +516,63 @@ echo " ✅ $MSG_SUCCESS"
420
516
  echo -e "${NC}${GREEN}${LINE}${NC}"
421
517
  echo ""
422
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
+
423
533
  echo -e "${YELLOW}${BOLD}$MSG_NEXT_STEPS${NC}"
424
534
  echo ""
425
535
 
426
536
  if [ "$LANG" = "es" ]; then
427
- 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}"
428
541
  echo " ${CYAN}https://github.com/apps/coderabbitai${NC}"
429
542
  echo ""
430
- echo " ${BOLD}2. Ejecutar cuestionario (20 min):${NC}"
543
+ echo " ${BOLD}3. Ejecutar cuestionario (20 min):${NC}"
431
544
  echo " ${GREEN}./scripts/generate-contrato.sh${NC}"
432
545
  echo ""
433
- echo " ${BOLD}3. Push a GitHub:${NC}"
546
+ echo " ${BOLD}4. Push a GitHub:${NC}"
434
547
  echo " ${GREEN}git init && git add . && git commit -m 'init: SpecLeap setup'${NC}"
435
548
  echo " ${GREEN}git push origin main${NC}"
436
549
  echo ""
437
- echo " ${BOLD}4. Abrir en tu IDE:${NC}"
550
+ echo " ${BOLD}5. Abrir en tu IDE:${NC}"
438
551
  echo " ${GREEN}code .${NC} (VSCode)"
439
552
  echo " ${GREEN}cursor .${NC} (Cursor)"
440
553
  echo ""
441
- echo " ${BOLD}5. En el chat con la IA, escribe:${NC}"
554
+ echo " ${BOLD}6. En el chat con la IA, escribe:${NC}"
442
555
  echo " ${GREEN}ayuda${NC}"
443
556
  echo ""
444
557
  else
445
- 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}"
446
562
  echo " ${CYAN}https://github.com/apps/coderabbitai${NC}"
447
563
  echo ""
448
- echo " ${BOLD}2. Run questionnaire (20 min):${NC}"
564
+ echo " ${BOLD}3. Run questionnaire (20 min):${NC}"
449
565
  echo " ${GREEN}./scripts/generate-contrato.sh${NC}"
450
566
  echo ""
451
- echo " ${BOLD}3. Push to GitHub:${NC}"
567
+ echo " ${BOLD}4. Push to GitHub:${NC}"
452
568
  echo " ${GREEN}git init && git add . && git commit -m 'init: SpecLeap setup'${NC}"
453
569
  echo " ${GREEN}git push origin main${NC}"
454
570
  echo ""
455
- echo " ${BOLD}4. Open in your IDE:${NC}"
571
+ echo " ${BOLD}5. Open in your IDE:${NC}"
456
572
  echo " ${GREEN}code .${NC} (VSCode)"
457
573
  echo " ${GREEN}cursor .${NC} (Cursor)"
458
574
  echo ""
459
- echo " ${BOLD}5. In AI chat, type:${NC}"
575
+ echo " ${BOLD}6. In AI chat, type:${NC}"
460
576
  echo " ${GREEN}ayuda${NC}"
461
577
  echo ""
462
578
  fi