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 +40 -0
- package/README.md +1 -1
- package/bin/specleap +122 -33
- package/package.json +1 -1
- package/setup.sh +187 -71
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.
|
|
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
|
-
//
|
|
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
|
-
//
|
|
11
|
-
|
|
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
|
-
//
|
|
14
|
-
|
|
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('
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
53
|
+
rl.question('Carpeta base / Base folder: ', (answer) => {
|
|
54
|
+
rl.close();
|
|
55
|
+
resolve(resolveBasePath(answer));
|
|
56
|
+
});
|
|
57
|
+
});
|
|
23
58
|
}
|
|
24
59
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
//
|
|
41
|
-
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
49
|
-
|
|
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
package/setup.sh
CHANGED
|
@@ -167,22 +167,70 @@ else
|
|
|
167
167
|
echo ""
|
|
168
168
|
fi
|
|
169
169
|
|
|
170
|
-
|
|
171
|
-
|
|
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
|
-
|
|
174
|
-
|
|
175
|
-
exit 1
|
|
176
|
-
fi
|
|
175
|
+
while [ -z "$GITHUB_USER" ]; do
|
|
176
|
+
GITHUB_ATTEMPT=$((GITHUB_ATTEMPT + 1))
|
|
177
177
|
|
|
178
|
-
|
|
179
|
-
echo
|
|
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 "$
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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
|
-
|
|
239
|
-
|
|
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
|
-
|
|
243
|
-
echo -e "${GREEN}✅ Token válido${NC}"
|
|
286
|
+
read -p "$(echo -e ${YELLOW}Asana Token:${NC} )" ASANA_TOKEN
|
|
244
287
|
echo ""
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
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
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
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
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
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
|
-
|
|
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
|
|
286
|
-
|
|
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.
|
|
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}
|
|
543
|
+
echo " ${BOLD}3. Ejecutar cuestionario (20 min):${NC}"
|
|
431
544
|
echo " ${GREEN}./scripts/generate-contrato.sh${NC}"
|
|
432
545
|
echo ""
|
|
433
|
-
echo " ${BOLD}
|
|
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}
|
|
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}
|
|
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.
|
|
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}
|
|
564
|
+
echo " ${BOLD}3. Run questionnaire (20 min):${NC}"
|
|
449
565
|
echo " ${GREEN}./scripts/generate-contrato.sh${NC}"
|
|
450
566
|
echo ""
|
|
451
|
-
echo " ${BOLD}
|
|
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}
|
|
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}
|
|
575
|
+
echo " ${BOLD}6. In AI chat, type:${NC}"
|
|
460
576
|
echo " ${GREEN}ayuda${NC}"
|
|
461
577
|
echo ""
|
|
462
578
|
fi
|