nexakit 0.1.0 → 0.2.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.
- package/.claude-plugin/marketplace.json +18 -1
- package/README.md +40 -30
- package/assets/brand/README.md +0 -2
- package/assets/runtime/package-lock.json +2 -2
- package/bin/agent-task.js +12 -10
- package/bin/agent-toolkit-gemini.js +11 -9
- package/bin/agent-toolkit-inbox.js +12 -10
- package/bin/agent-toolkit-mcp.js +14 -8
- package/bin/agent-toolkit.js +12 -9
- package/bin/nexakit.js +12 -9
- package/bin/project-memory-mcp.js +12 -9
- package/bin/project-memory.js +5 -3
- package/{skills/depurar → catalogs/skills/vercel-composition-patterns}/LICENSE.txt +6 -6
- package/catalogs/skills/vercel-react-best-practices/LICENSE.txt +27 -0
- package/catalogs/skills.json +70 -35
- package/docs/skills/evidencias/2026-10-28-recomendador-preparacion.md +1 -1
- package/package.json +5 -3
- package/plugins/nexakit-modelo/.claude-plugin/plugin.json +1 -0
- package/plugins/nexakit-modelo/hooks/hooks.json +1 -0
- package/plugins/nexakit-modelo/hooks/register.js +48 -0
- package/plugins/nexakit-modelo/hooks/status.js +8 -0
- package/plugins/nexakit-modelo/tests/register.test.ts +21 -0
- package/plugins/nexakit-panel/.claude-plugin/plugin.json +1 -0
- package/plugins/nexakit-panel/README.md +20 -0
- package/plugins/nexakit-panel/hooks/hooks.json +1 -0
- package/plugins/nexakit-panel/hooks/lienzo.js +382 -0
- package/plugins/nexakit-panel/hooks/panel.js +279 -0
- package/plugins/nexakit-panel/hooks/register.tsx +248 -0
- package/plugins/nexakit-panel/tests/lienzo.test.ts +142 -0
- package/plugins/nexakit-panel/tests/panel.test.ts +240 -0
- package/plugins/nexakit-panel/tests/register.test.ts +167 -0
- package/plugins/nexakit-panel/types/index.d.ts +41 -0
- package/plugins/nexakit-sesion/.claude-plugin/plugin.json +1 -0
- package/plugins/nexakit-sesion/README.md +12 -0
- package/plugins/nexakit-sesion/hooks/aviso.js +38 -0
- package/plugins/nexakit-sesion/hooks/hooks.json +1 -0
- package/plugins/nexakit-sesion/hooks/register.js +28 -0
- package/plugins/nexakit-sesion/tests/register.test.ts +96 -0
- package/schemas/skill-catalog.schema.json +9 -2
- package/skills/cerrar-memoria/SKILL.md +6 -2
- package/skills/cerrar-memoria/references/cierre.md +5 -1
- package/skills/elegir-modelo/SKILL.md +68 -0
- package/skills/elegir-modelo/modelos.mjs +108 -0
- package/skills/gemini/SKILL.md +1 -1
- package/skills/project-handoff/SKILL.md +39 -12
- package/skills/project-handoff/references/handoff-template.md +24 -17
- package/skills/quality-gate/SKILL.md +6 -12
- package/skills/revisar/SKILL.md +6 -16
- package/skills/{desarrollo-con-pruebas/LICENSE.txt → revisar-skill/LICENSE-find-skills.txt} +5 -5
- package/skills/revisar-skill/SKILL.md +94 -0
- package/skills/revision-cruzada/SKILL.md +53 -475
- package/skills/web-design-guidelines/AGENTS.md +156 -0
- package/skills/{spec-driven-development → web-design-guidelines}/LICENSE.txt +1 -6
- package/skills/web-design-guidelines/SKILL.md +20 -30
- package/src/agent-models.js +53 -13
- package/src/brand.js +117 -0
- package/src/cli-executable.js +5 -3
- package/src/cli.js +16 -9
- package/src/connect.js +56 -4
- package/src/init.js +9 -1
- package/src/install-lock.js +127 -0
- package/src/install-registry.js +61 -0
- package/src/legacy-repair.js +71 -0
- package/src/link-states.js +172 -0
- package/src/memory-locator.js +118 -0
- package/src/missing-dependencies.js +21 -0
- package/src/model-command.js +147 -0
- package/src/panel-cli.js +182 -0
- package/src/project-root.js +41 -0
- package/src/project-signals.js +339 -0
- package/src/recommend-report.js +77 -0
- package/src/review-instructions.js +211 -0
- package/src/review-policy.js +35 -0
- package/src/revision-adapters.js +157 -0
- package/src/revision-context.js +86 -0
- package/src/revision-isolation.js +222 -0
- package/src/revision-process.js +103 -0
- package/src/revision-provenance.js +89 -0
- package/src/revision-snapshot.js +120 -0
- package/src/revision.js +242 -0
- package/src/setup-apply.js +231 -0
- package/src/setup-plan.js +231 -0
- package/src/setup-state.js +277 -0
- package/src/setup-tui.js +555 -0
- package/src/skill-catalog-add.js +91 -0
- package/src/skill-catalog-audit.js +26 -27
- package/src/skill-files.js +39 -0
- package/src/skill-recommendations.js +321 -208
- package/src/skill-scan-report.js +14 -0
- package/src/skill-scan.js +178 -0
- package/src/stable-command.js +53 -0
- package/src/stable-install.js +222 -0
- package/src/superpowers.js +133 -0
- package/src/sync-apply.js +8 -5
- package/src/sync-files.js +2 -1
- package/src/terminal-text.js +68 -0
- package/src/toolkit-cli.js +397 -70
- package/src/toolkit-commands-guide.js +53 -39
- package/src/toolkit-setup.js +507 -110
- package/assets/brand/agent-toolkit-mark-v1.png +0 -0
- package/skills/depurar/SKILL.md +0 -69
- package/skills/depurar/agents/openai.yaml +0 -7
- package/skills/depurar/references/tecnicas-de-diagnostico.md +0 -37
- package/skills/desarrollo-con-pruebas/SKILL.md +0 -55
- package/skills/desarrollo-con-pruebas/agents/openai.yaml +0 -7
- package/skills/desarrollo-con-pruebas/references/buenas-pruebas.md +0 -33
- package/skills/estado/SKILL.md +0 -41
- package/skills/programar/SKILL.md +0 -19
- package/skills/spec-driven-development/SKILL.md +0 -48
- package/skills/spec-driven-development/agents/openai.yaml +0 -7
- package/skills/spec-driven-development/references/spec-template.md +0 -55
- package/skills/spec-driven-development/references/workflow.md +0 -70
- package/src/toolkit-tui.js +0 -961
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// Text helpers shared by nexakit's terminal screens: color that honours NO_COLOR, the width a character really
|
|
2
|
+
// takes in a terminal cell, wrapping, and cutting a line to fit.
|
|
3
|
+
export function style(colorEnabled) {
|
|
4
|
+
if (!colorEnabled) {
|
|
5
|
+
const plain = text => String(text);
|
|
6
|
+
return { bold: plain, cyan: plain, green: plain, yellow: plain, dim: plain, red: plain, magenta: plain };
|
|
7
|
+
}
|
|
8
|
+
const wrap = (open, close) => text => `\x1b[${open}m${text}\x1b[${close}m`;
|
|
9
|
+
return {
|
|
10
|
+
bold: wrap(1, 22),
|
|
11
|
+
dim: wrap(2, 22),
|
|
12
|
+
red: wrap(31, 39),
|
|
13
|
+
green: wrap(32, 39),
|
|
14
|
+
yellow: wrap(33, 39),
|
|
15
|
+
magenta: wrap(35, 39),
|
|
16
|
+
cyan: wrap(36, 39)
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function cellWidth(text) {
|
|
21
|
+
return [...text].reduce((width, char) => {
|
|
22
|
+
const cp = char.codePointAt(0);
|
|
23
|
+
if (/\p{Mark}/u.test(char) || cp === 0x200d || cp === 0xfe0f) return width;
|
|
24
|
+
const wide = /\p{Extended_Pictographic}/u.test(char)
|
|
25
|
+
|| (cp >= 0x1100 && (cp <= 0x115f || cp === 0x2329 || cp === 0x232a
|
|
26
|
+
|| (cp >= 0x2e80 && cp <= 0xa4cf) || (cp >= 0xac00 && cp <= 0xd7a3)
|
|
27
|
+
|| (cp >= 0xf900 && cp <= 0xfaff) || (cp >= 0xfe10 && cp <= 0xfe19)
|
|
28
|
+
|| (cp >= 0xfe30 && cp <= 0xfe6f) || (cp >= 0xff00 && cp <= 0xff60)
|
|
29
|
+
|| (cp >= 0xffe0 && cp <= 0xffe6) || cp >= 0x20000));
|
|
30
|
+
return width + (wide ? 2 : 1);
|
|
31
|
+
}, 0);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function wrapText(text, width) {
|
|
35
|
+
const lines = [];
|
|
36
|
+
for (const paragraph of String(text).split('\n')) {
|
|
37
|
+
let line = '';
|
|
38
|
+
for (const token of paragraph.match(/\s*\S+/gu) ?? []) {
|
|
39
|
+
const word = token.trimStart();
|
|
40
|
+
let gap = token.slice(0, token.length - word.length);
|
|
41
|
+
if (line && cellWidth(line + gap + word) > width) {
|
|
42
|
+
lines.push(line.trimEnd());
|
|
43
|
+
line = '';
|
|
44
|
+
gap = '';
|
|
45
|
+
}
|
|
46
|
+
for (const char of gap + word) {
|
|
47
|
+
if (cellWidth(line + char) > width) {
|
|
48
|
+
lines.push(line);
|
|
49
|
+
line = '';
|
|
50
|
+
}
|
|
51
|
+
line += char;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
lines.push(line);
|
|
55
|
+
}
|
|
56
|
+
return lines;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Cuts a line to `width` cells, ending in "…" when something was cut.
|
|
60
|
+
export function fitLine(text, width) {
|
|
61
|
+
if (cellWidth(text) <= width) return text;
|
|
62
|
+
let line = '';
|
|
63
|
+
for (const char of text) {
|
|
64
|
+
if (cellWidth(line + char) > width - 1) break;
|
|
65
|
+
line += char;
|
|
66
|
+
}
|
|
67
|
+
return `${line}…`;
|
|
68
|
+
}
|
package/src/toolkit-cli.js
CHANGED
|
@@ -1,99 +1,148 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import { runCli } from './cli.js';
|
|
3
|
+
import { runModelCommand } from './model-command.js';
|
|
4
|
+
import { runPanelCommand } from './panel-cli.js';
|
|
5
|
+
import { runRevisionCommand } from './revision.js';
|
|
1
6
|
import path from 'node:path';
|
|
7
|
+
import readline from 'node:readline';
|
|
2
8
|
import { pathToFileURL } from 'node:url';
|
|
9
|
+
import { resolveProjectRoot } from './project-root.js';
|
|
3
10
|
import { loadSkillCatalog } from './skill-catalog.js';
|
|
11
|
+
import { scanSkillDirectory } from './skill-scan.js';
|
|
12
|
+
import { formatScanReport } from './skill-scan-report.js';
|
|
4
13
|
import {
|
|
5
14
|
applyToolkitDetach,
|
|
6
15
|
formatDetachReport,
|
|
7
16
|
inspectSkillProject,
|
|
8
17
|
planToolkitDetach,
|
|
9
|
-
recommendSkills
|
|
18
|
+
recommendSkills,
|
|
10
19
|
} from './skill-recommendations.js';
|
|
11
|
-
import { formatRecommendationReport
|
|
20
|
+
import { formatRecommendationReport } from './recommend-report.js';
|
|
21
|
+
import { applySetup } from './setup-apply.js';
|
|
22
|
+
import { buildConfirmation, suggestedAnswers } from './setup-plan.js';
|
|
23
|
+
import { gatherSetupState } from './setup-state.js';
|
|
24
|
+
import { runSetupTui } from './setup-tui.js';
|
|
12
25
|
import { doctorToolkit, setupToolkit } from './toolkit-setup.js';
|
|
26
|
+
import { applyLegacyRepair, planLegacyRepair } from './legacy-repair.js';
|
|
27
|
+
import { READY_LINK_STATES, REMOVED_BY_USER_STATE, SETTLED_LINK_STATES } from './link-states.js';
|
|
13
28
|
|
|
14
29
|
const PROFILE_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/u;
|
|
30
|
+
const SKILL_NAME_RE = /^[a-z0-9-]+$/u;
|
|
15
31
|
const RECOMMEND_PROFILES = ['frontend', 'backend', 'mcp', 'security'];
|
|
16
32
|
|
|
17
|
-
const HELP = `Uso: npx nexakit [
|
|
33
|
+
const HELP = `Uso: npx nexakit [--project <ruta>] [--si]
|
|
34
|
+
npx nexakit comprobar [--agent codex|claude|antigravity|all] [--perfil-codex <nombre>] [--json]
|
|
35
|
+
npx nexakit quitar [--project <ruta>] [--global] [--agent codex|claude|antigravity|all] [--si|--apply] [--json]
|
|
36
|
+
npx nexakit reparar --desde <ruta> [--agent codex|claude|antigravity|all]
|
|
37
|
+
npx nexakit memoria <subcomando> [opciones]
|
|
38
|
+
nexakit revision --disponibles [--principal claude|codex|antigravity]
|
|
39
|
+
nexakit revision --con <ia> --modelo <m> --esfuerzo <e> --encargo <archivo> [--base <rama>]
|
|
40
|
+
nexakit modelo [--nivel <nivel>] [--ia claude|codex|antigravity] [--perfil-codex <nombre>]
|
|
41
|
+
nexakit panel [--demo] [--una-vez]
|
|
18
42
|
npx nexakit recommend [--project <ruta>] [--profile frontend|backend|mcp|security] [--client codex|claude] [--dry-run] [--json]
|
|
19
|
-
npx nexakit
|
|
20
|
-
npx nexakit doctor [--agent codex|claude|all] [--perfil-codex <nombre>] [--json]
|
|
21
|
-
npx nexakit setup [--agent codex|claude|all] [--perfil-codex <nombre>] [--apply] [--json]
|
|
43
|
+
npx nexakit scan <carpeta de skill> [--json]
|
|
44
|
+
npx nexakit doctor [--agent codex|claude|antigravity|all] [--perfil-codex <nombre>] [--json]
|
|
45
|
+
npx nexakit setup [--agent codex|claude|antigravity|all] [--perfil-codex <nombre>] [--omitir <skill>] [--reponer <skill>] [--apply] [--json]
|
|
22
46
|
|
|
47
|
+
npx nexakit hace tres preguntas con la respuesta ya sugerida (skills, memoria e IA)
|
|
48
|
+
y, con una sola confirmación, deja listo el proyecto: skills, memoria conectada a
|
|
49
|
+
tus IA y la orden ~/.nexakit/nexakit. Si todo está listo, no pregunta nada.
|
|
50
|
+
Sin terminal interactiva muestra lo que haría; --si lo aplica sin preguntas.
|
|
51
|
+
comprobar (o doctor) revisa en solo lectura Node, tus IA, skills base y restos antiguos.
|
|
52
|
+
quitar (o detach) retira las skills del proyecto, .agent-toolkit/ y su bloque de
|
|
53
|
+
.gitignore sin tocar archivos tuyos (--si o --apply para ejecutar; --global incluye las globales).
|
|
54
|
+
scan revisa el contenido de una skill sin ejecutarla (1 si encuentra un bloqueo).
|
|
55
|
+
reparar re-apunta enlaces rotos de una instalación anterior (--desde es la carpeta
|
|
56
|
+
que ya no existe); lista lo que encontró y siempre pregunta.
|
|
57
|
+
memoria ejecuta los subcomandos de project-memory (init, connect, doctor, status, sync).
|
|
58
|
+
revision y modelo piden una revisión a otra IA o consultan los modelos disponibles.
|
|
59
|
+
panel dibuja a pantalla completa la orquesta de tu sesión de Claude Code (ábrelo en otra división).
|
|
60
|
+
recommend inspecciona el proyecto en solo lectura; setup prepara las skills base globales y respeta las que quitaste
|
|
61
|
+
(--omitir <skill> declara quitada una que ya no tiene enlace; --reponer <skill> devuelve una; ambos se repiten).
|
|
23
62
|
(Alias local compatible: agent-toolkit)
|
|
24
|
-
En una terminal interactiva, npx nexakit abre el asistente por pasos:
|
|
25
|
-
detecta el stack del proyecto, sugiere skills verificadas por SHA-256,
|
|
26
|
-
guarda su estado aislado e ignorado en Git bajo .agent-toolkit/ y aplica
|
|
27
|
-
instalaciones o desacoplados ([d]) tras vista previa segura.
|
|
28
|
-
recommend inspecciona el proyecto en solo lectura sin escribir ni usar red.
|
|
29
|
-
detach retira las skills acopladas, .agent-toolkit/ y el bloque de ignore sin
|
|
30
|
-
tocar archivos propios (--apply para ejecutar; --global incluye ~/.claude y ~/.codex).
|
|
31
|
-
setup prepara enlaces globales de las skills base; --apply los crea sin reemplazar
|
|
32
|
-
archivos existentes. Imprime los comandos MCP para registrar por separado.
|
|
33
|
-
doctor comprueba instalación local sin iniciar modelos. Reinicia los clientes
|
|
34
|
-
tras registrar el MCP. Con Codex, también indica si su sandbox deja escribir en
|
|
35
|
-
~/.agent-toolkit (lo necesita $revisar) y el comando para permitirlo. Si abres
|
|
36
|
-
Codex con --profile, indica ese perfil con --perfil-codex.
|
|
37
|
-
Superpowers es un plugin opcional independiente.
|
|
38
63
|
`;
|
|
39
64
|
|
|
40
65
|
export async function runToolkitCli(
|
|
41
66
|
argv,
|
|
42
|
-
{ out, err, input = process.stdin, output = process.stdout, isTTY, options = {} } = {}
|
|
67
|
+
{ out, err, input = process.stdin, output = process.stdout, isTTY, options = {} } = {},
|
|
43
68
|
) {
|
|
44
69
|
if (argv.length === 1 && ['--help', '-h'].includes(argv[0])) {
|
|
45
70
|
await out(HELP);
|
|
46
71
|
return 0;
|
|
47
72
|
}
|
|
48
73
|
|
|
49
|
-
if (argv.length === 0 || argv[0] === 'tui') {
|
|
50
|
-
const rest = argv[0] === 'tui' ? argv.slice(1) :
|
|
51
|
-
const parsed = {
|
|
74
|
+
if (argv.length === 0 || argv[0] === 'tui' || argv[0].startsWith('--')) {
|
|
75
|
+
const rest = argv[0] === 'tui' ? argv.slice(1) : argv;
|
|
76
|
+
const parsed = { projectRoot: process.cwd(), yes: false };
|
|
52
77
|
const seen = new Set();
|
|
53
78
|
for (let i = 0; i < rest.length; i++) {
|
|
54
79
|
const flag = rest[i];
|
|
55
|
-
if (seen.has(flag)) {
|
|
80
|
+
if (seen.has(flag)) {
|
|
81
|
+
await err(HELP);
|
|
82
|
+
return 2;
|
|
83
|
+
}
|
|
56
84
|
seen.add(flag);
|
|
57
85
|
if (flag === '--project' && rest[i + 1]) parsed.projectRoot = rest[++i];
|
|
58
|
-
else if (flag === '--
|
|
59
|
-
else
|
|
60
|
-
|
|
86
|
+
else if (flag === '--si') parsed.yes = true;
|
|
87
|
+
else {
|
|
88
|
+
await err(HELP);
|
|
89
|
+
return 2;
|
|
90
|
+
}
|
|
61
91
|
}
|
|
62
|
-
|
|
63
|
-
|
|
92
|
+
return runSetup(parsed, { out, err, input, output, isTTY, options });
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const [command, ...rest] = argv;
|
|
96
|
+
if (command === 'modelo') return runModelCommand(rest, { out, err }, options);
|
|
97
|
+
if (command === 'revision') return runRevisionCommand(rest, { out, err }, { options });
|
|
98
|
+
if (command === 'panel') return runPanelCommand(rest, { out, err, input, output, isTTY, options });
|
|
99
|
+
if (command === 'memoria') {
|
|
100
|
+
return runCli(
|
|
101
|
+
rest,
|
|
102
|
+
{
|
|
103
|
+
out: (message) => out(`${message}\n`),
|
|
104
|
+
err: (message) => err(`${message}\n`),
|
|
105
|
+
},
|
|
106
|
+
{ ...options, commandName: 'npx nexakit memoria' },
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (command === 'scan') {
|
|
111
|
+
const flags = rest.filter(arg => arg.startsWith('--'));
|
|
112
|
+
const dirs = rest.filter(arg => !arg.startsWith('--'));
|
|
113
|
+
if (dirs.length !== 1 || flags.some(flag => flag !== '--json') || flags.length > 1) {
|
|
64
114
|
await err(HELP);
|
|
65
115
|
return 2;
|
|
66
116
|
}
|
|
67
117
|
try {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
setupOptions: options
|
|
75
|
-
});
|
|
76
|
-
} catch {
|
|
77
|
-
await err('No se pudo abrir la interfaz interactiva. Revisa la ruta del proyecto y los permisos.\n');
|
|
78
|
-
return 1;
|
|
118
|
+
const report = await scanSkillDirectory(dirs[0]);
|
|
119
|
+
await out(flags.length ? JSON.stringify(report, null, 2) + '\n' : formatScanReport(report, dirs[0]));
|
|
120
|
+
return report.blocked ? 1 : 0;
|
|
121
|
+
} catch (error) {
|
|
122
|
+
await err(`No se pudo escanear ${dirs[0]}: ${error.code === 'ENOENT' ? 'la carpeta no existe' : error.message}\n`);
|
|
123
|
+
return 2;
|
|
79
124
|
}
|
|
80
125
|
}
|
|
81
126
|
|
|
82
|
-
const [command, ...rest] = argv;
|
|
83
|
-
|
|
84
127
|
if (command === 'recommend') {
|
|
85
128
|
const parsed = { projectRoot: process.cwd(), json: false };
|
|
86
129
|
const seen = new Set();
|
|
87
130
|
for (let i = 0; i < rest.length; i++) {
|
|
88
131
|
const flag = rest[i];
|
|
89
|
-
if (seen.has(flag)) {
|
|
132
|
+
if (seen.has(flag)) {
|
|
133
|
+
await err(HELP);
|
|
134
|
+
return 2;
|
|
135
|
+
}
|
|
90
136
|
seen.add(flag);
|
|
91
137
|
if (flag === '--project' && rest[i + 1]) parsed.projectRoot = rest[++i];
|
|
92
138
|
else if (flag === '--profile' && RECOMMEND_PROFILES.includes(rest[i + 1])) parsed.profile = rest[++i];
|
|
93
139
|
else if (flag === '--client' && ['codex', 'claude'].includes(rest[i + 1])) parsed.client = rest[++i];
|
|
94
140
|
else if (flag === '--dry-run') parsed.dryRun = true;
|
|
95
141
|
else if (flag === '--json') parsed.json = true;
|
|
96
|
-
else {
|
|
142
|
+
else {
|
|
143
|
+
await err(HELP);
|
|
144
|
+
return 2;
|
|
145
|
+
}
|
|
97
146
|
}
|
|
98
147
|
try {
|
|
99
148
|
const catalogUrl = options.toolkitRoot
|
|
@@ -103,13 +152,13 @@ export async function runToolkitCli(
|
|
|
103
152
|
const inspection = await inspectSkillProject(parsed.projectRoot, {
|
|
104
153
|
...(parsed.client && { client: parsed.client }),
|
|
105
154
|
...(options.toolkitRoot && { toolkitRoot: options.toolkitRoot }),
|
|
106
|
-
catalog
|
|
155
|
+
catalog,
|
|
107
156
|
});
|
|
108
157
|
const report = recommendSkills({
|
|
109
158
|
catalog,
|
|
110
159
|
inspection,
|
|
111
160
|
...(parsed.profile && { profile: parsed.profile }),
|
|
112
|
-
...(parsed.client && { client: parsed.client })
|
|
161
|
+
...(parsed.client && { client: parsed.client }),
|
|
113
162
|
});
|
|
114
163
|
await out(parsed.json ? JSON.stringify(report) + '\n' : formatRecommendationReport(report));
|
|
115
164
|
return 0;
|
|
@@ -119,38 +168,54 @@ export async function runToolkitCli(
|
|
|
119
168
|
}
|
|
120
169
|
}
|
|
121
170
|
|
|
122
|
-
if (command === '
|
|
171
|
+
if (command === 'reparar') return runRepair(rest, { out, err, input, output, isTTY, options });
|
|
172
|
+
|
|
173
|
+
if (command === 'detach' || command === 'unlink' || command === 'quitar') {
|
|
123
174
|
const parsed = { projectRoot: process.cwd(), agent: 'all', global: false, apply: false, json: false };
|
|
124
175
|
const seen = new Set();
|
|
125
176
|
for (let i = 0; i < rest.length; i++) {
|
|
126
177
|
const flag = rest[i];
|
|
127
|
-
if (seen.has(flag)) {
|
|
178
|
+
if (seen.has(flag)) {
|
|
179
|
+
await err(HELP);
|
|
180
|
+
return 2;
|
|
181
|
+
}
|
|
128
182
|
seen.add(flag);
|
|
129
183
|
if (flag === '--project' && rest[i + 1]) parsed.projectRoot = rest[++i];
|
|
130
|
-
else if (flag === '--agent' && ['codex', 'claude', 'all'].includes(rest[i + 1]))
|
|
184
|
+
else if (flag === '--agent' && ['codex', 'claude', 'antigravity', 'all'].includes(rest[i + 1]))
|
|
185
|
+
parsed.agent = rest[++i];
|
|
131
186
|
else if (flag === '--global') parsed.global = true;
|
|
132
|
-
else if (flag === '--apply') parsed.apply = true;
|
|
187
|
+
else if (flag === '--apply' || flag === '--si') parsed.apply = true;
|
|
133
188
|
else if (flag === '--json') parsed.json = true;
|
|
134
|
-
else {
|
|
189
|
+
else {
|
|
190
|
+
await err(HELP);
|
|
191
|
+
return 2;
|
|
192
|
+
}
|
|
135
193
|
}
|
|
136
194
|
try {
|
|
137
195
|
const catalogUrl = options.toolkitRoot
|
|
138
196
|
? pathToFileURL(path.join(options.toolkitRoot, 'catalogs/skills.json'))
|
|
139
197
|
: undefined;
|
|
140
198
|
const catalog = await loadSkillCatalog(catalogUrl ? { catalogUrl } : {});
|
|
199
|
+
// Skills are installed at the repository root, so they are removed from there too.
|
|
200
|
+
const { root } = await resolveProjectRoot(
|
|
201
|
+
parsed.projectRoot,
|
|
202
|
+
options.homeDirectory ? { homeDirectory: options.homeDirectory } : {},
|
|
203
|
+
);
|
|
141
204
|
const plan = await planToolkitDetach({
|
|
142
|
-
projectRoot:
|
|
205
|
+
projectRoot: root,
|
|
143
206
|
includeGlobal: parsed.global,
|
|
207
|
+
environment: options.environment ?? (options.homeDirectory ? {} : process.env),
|
|
144
208
|
agent: parsed.agent,
|
|
145
209
|
catalog,
|
|
146
210
|
...(options.toolkitRoot && { toolkitRoot: options.toolkitRoot }),
|
|
147
211
|
...(options.homeDirectory && { homeDirectory: options.homeDirectory }),
|
|
148
|
-
...(options.codexHome && { codexHome: options.codexHome })
|
|
212
|
+
...(options.codexHome && { codexHome: options.codexHome }),
|
|
149
213
|
});
|
|
150
214
|
const report = parsed.apply ? await applyToolkitDetach(plan) : plan;
|
|
151
215
|
if (parsed.json) {
|
|
152
216
|
const cleanJson = {
|
|
153
217
|
applied: report.applied,
|
|
218
|
+
reviewInstructions: report.reviewInstructions ?? [],
|
|
154
219
|
projectRoot: report.projectRoot,
|
|
155
220
|
includeGlobal: report.includeGlobal,
|
|
156
221
|
items: report.items.map(({ catalogEntry, ...itemRest }) => itemRest),
|
|
@@ -158,15 +223,15 @@ export async function runToolkitCli(
|
|
|
158
223
|
? {
|
|
159
224
|
removedCount: report.removedCount,
|
|
160
225
|
gitIgnoreCleanedCount: report.gitIgnoreCleanedCount,
|
|
161
|
-
stateDirRemoved: report.stateDirRemoved
|
|
226
|
+
stateDirRemoved: report.stateDirRemoved,
|
|
162
227
|
}
|
|
163
228
|
: {
|
|
164
229
|
removeStateDir: report.removeStateDir,
|
|
165
230
|
removeLegacyLockfile: report.removeLegacyLockfile,
|
|
166
|
-
gitIgnoreUpdates: report.gitIgnoreUpdates
|
|
231
|
+
gitIgnoreUpdates: report.gitIgnoreUpdates,
|
|
167
232
|
}),
|
|
168
233
|
conflicts: report.conflicts,
|
|
169
|
-
manualCommands: report.manualCommands
|
|
234
|
+
manualCommands: report.manualCommands,
|
|
170
235
|
};
|
|
171
236
|
await out(JSON.stringify(cleanJson) + '\n');
|
|
172
237
|
} else {
|
|
@@ -179,7 +244,7 @@ export async function runToolkitCli(
|
|
|
179
244
|
}
|
|
180
245
|
}
|
|
181
246
|
|
|
182
|
-
if (!['doctor', 'setup'].includes(command)) {
|
|
247
|
+
if (!['doctor', 'comprobar', 'setup'].includes(command)) {
|
|
183
248
|
await err(HELP);
|
|
184
249
|
return 2;
|
|
185
250
|
}
|
|
@@ -188,29 +253,291 @@ export async function runToolkitCli(
|
|
|
188
253
|
const seen = new Set();
|
|
189
254
|
for (let i = 0; i < rest.length; i++) {
|
|
190
255
|
const flag = rest[i];
|
|
191
|
-
if (
|
|
256
|
+
if ((flag === '--reponer' || flag === '--omitir') && command === 'setup') {
|
|
257
|
+
const name = rest[i + 1] ?? '';
|
|
258
|
+
if (!SKILL_NAME_RE.test(name)) {
|
|
259
|
+
await err(HELP);
|
|
260
|
+
return 2;
|
|
261
|
+
}
|
|
262
|
+
(parsed[flag === '--reponer' ? 'restore' : 'omit'] ??= []).push(name);
|
|
263
|
+
i++;
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
if (seen.has(flag)) {
|
|
267
|
+
await err(HELP);
|
|
268
|
+
return 2;
|
|
269
|
+
}
|
|
192
270
|
seen.add(flag);
|
|
193
|
-
if (flag === '--agent' && ['codex', 'claude', 'all'].includes(rest[i + 1]))
|
|
271
|
+
if (flag === '--agent' && ['codex', 'claude', 'antigravity', 'all'].includes(rest[i + 1]))
|
|
272
|
+
parsed.agent = rest[++i];
|
|
194
273
|
else if (flag === '--json') parsed.json = true;
|
|
195
274
|
else if (flag === '--perfil-codex' && PROFILE_RE.test(rest[i + 1] ?? '')) parsed.codexProfile = rest[++i];
|
|
196
275
|
else if (flag === '--apply' && command === 'setup') parsed.apply = true;
|
|
197
|
-
else {
|
|
276
|
+
else {
|
|
277
|
+
await err(HELP);
|
|
278
|
+
return 2;
|
|
279
|
+
}
|
|
198
280
|
}
|
|
199
281
|
try {
|
|
200
282
|
const result = await (command === 'setup' ? setupToolkit : doctorToolkit)({ ...options, ...parsed });
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
283
|
+
if (command === 'comprobar' && !parsed.json) {
|
|
284
|
+
await out(formatComprobarReport(result, { homeDirectory: options.homeDirectory ?? os.homedir() }));
|
|
285
|
+
} else {
|
|
286
|
+
await out(JSON.stringify(result, null, parsed.json ? 0 : 2) + '\n');
|
|
287
|
+
}
|
|
288
|
+
if (command === 'setup') return result.conflicts ? 1 : 0;
|
|
289
|
+
if (command === 'comprobar') {
|
|
290
|
+
return result.node.supported &&
|
|
205
291
|
Object.values(result.dependencies).every(Boolean) &&
|
|
206
|
-
Object.values(result.clients).
|
|
207
|
-
|
|
208
|
-
result.
|
|
209
|
-
(
|
|
292
|
+
Object.values(result.clients).some((c) => c.available) &&
|
|
293
|
+
result.skills.every((s) => SETTLED_LINK_STATES.includes(s.state)) &&
|
|
294
|
+
!(result.retiredSkills ?? []).some((s) => !s.owned) &&
|
|
295
|
+
superpowersReady(result)
|
|
210
296
|
? 0
|
|
211
297
|
: 1;
|
|
298
|
+
}
|
|
299
|
+
return result.node.supported &&
|
|
300
|
+
Object.values(result.dependencies).every(Boolean) &&
|
|
301
|
+
Object.values(result.clients).every((c) => c.available) &&
|
|
302
|
+
Object.values(result.mcp).every((m) => READY_LINK_STATES.includes(m.state)) &&
|
|
303
|
+
result.skills.every((s) => SETTLED_LINK_STATES.includes(s.state)) &&
|
|
304
|
+
(!result.codexSandbox || ['allowed', 'not-needed', 'unknown'].includes(result.codexSandbox.state)) &&
|
|
305
|
+
superpowersReady(result)
|
|
306
|
+
? 0
|
|
307
|
+
: 1;
|
|
308
|
+
} catch {
|
|
309
|
+
await err(
|
|
310
|
+
'No se pudo inspeccionar o instalar. Revisa rutas, permisos y conflictos; no se reemplazan destinos.\n',
|
|
311
|
+
);
|
|
312
|
+
return 1;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const plural = (n, one, many) => `${n} ${n === 1 ? one : many}`;
|
|
317
|
+
// Required: missing or disabled fails the check; a list that could not be read is reported but does not fail.
|
|
318
|
+
const superpowersReady = (result) =>
|
|
319
|
+
!Object.values(result.superpowers?.states ?? {}).some((state) => ['falta', 'desactivado'].includes(state));
|
|
320
|
+
|
|
321
|
+
function formatComprobarReport(result, { homeDirectory }) {
|
|
322
|
+
const short = (value) =>
|
|
323
|
+
value === homeDirectory ||
|
|
324
|
+
value.startsWith(homeDirectory + '/') ||
|
|
325
|
+
value.startsWith(homeDirectory + '\\')
|
|
326
|
+
? `~${value.slice(homeDirectory.length)}`
|
|
327
|
+
: value;
|
|
328
|
+
const lines = [
|
|
329
|
+
result.node.supported
|
|
330
|
+
? `✓ Node.js ${result.node.version}`
|
|
331
|
+
: `! Node.js ${result.node.version} (se necesita Node.js 24 o superior)`,
|
|
332
|
+
];
|
|
333
|
+
const missingDeps = Object.entries(result.dependencies)
|
|
334
|
+
.filter(([, ok]) => !ok)
|
|
335
|
+
.map(([name]) => name);
|
|
336
|
+
lines.push(
|
|
337
|
+
missingDeps.length === 0
|
|
338
|
+
? '✓ Dependencias instaladas'
|
|
339
|
+
: `! Faltan dependencias: ${missingDeps.join(', ')}`,
|
|
340
|
+
);
|
|
341
|
+
const availableClients = Object.entries(result.clients)
|
|
342
|
+
.filter(([, state]) => state.available)
|
|
343
|
+
.map(([name]) => name);
|
|
344
|
+
lines.push(
|
|
345
|
+
availableClients.length > 0
|
|
346
|
+
? `✓ IA disponibles: ${availableClients.join(', ')}`
|
|
347
|
+
: '! No encontré ninguna IA instalada (claude, codex o agy)',
|
|
348
|
+
);
|
|
349
|
+
const readySkills = result.skills.filter((s) => READY_LINK_STATES.includes(s.state));
|
|
350
|
+
const removedSkills = result.skills.filter((s) => s.state === REMOVED_BY_USER_STATE);
|
|
351
|
+
const pendingSkills = result.skills.filter((s) => !SETTLED_LINK_STATES.includes(s.state));
|
|
352
|
+
if (pendingSkills.length === 0) {
|
|
353
|
+
lines.push(`✓ Skills base listas (${readySkills.length})`);
|
|
354
|
+
} else {
|
|
355
|
+
lines.push(
|
|
356
|
+
`! ${plural(pendingSkills.length, 'enlace de skill base pendiente o con problema', 'enlaces de skills base pendientes o con problema')} (ejecuta npx nexakit para revisarlos)`,
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
if (removedSkills.length > 0) {
|
|
360
|
+
lines.push(
|
|
361
|
+
`✓ ${plural(removedSkills.length, 'skill que quitaste', 'skills que quitaste')} (nexakit no las recrea; para devolver una: npx nexakit setup --reponer <nombre> --apply)`,
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
for (const group of result.legacy ?? []) {
|
|
365
|
+
lines.push(
|
|
366
|
+
`! ${plural(group.count, 'enlace roto', 'enlaces rotos')} hacia ${short(group.root)} parecen de una instalación anterior. Si son de nexakit: npx nexakit reparar --desde ${short(group.root)}`,
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
for (const item of (result.retiredSkills ?? []).filter((s) => !s.owned)) {
|
|
370
|
+
lines.push(`! ${item.client} · ${item.name}: comando retirado con contenido ajeno; no lo toco`);
|
|
371
|
+
}
|
|
372
|
+
const superpowers = Object.entries(result.superpowers?.states ?? {});
|
|
373
|
+
const active = superpowers.filter(([, state]) => state === 'activo').map(([name]) => name);
|
|
374
|
+
if (active.length) lines.push(`✓ Superpowers activo en ${active.join(', ')}`);
|
|
375
|
+
for (const [name, state] of superpowers.filter(([, state]) => state !== 'activo')) {
|
|
376
|
+
lines.push(
|
|
377
|
+
state === 'falta'
|
|
378
|
+
? `! Falta Superpowers en ${name} (ejecuta npx nexakit para instalarlo)`
|
|
379
|
+
: state === 'desactivado'
|
|
380
|
+
? `! Superpowers está desactivado en ${name}; actívalo desde su gestor de plugins`
|
|
381
|
+
: `! No pude comprobar Superpowers en ${name}`,
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
for (const item of result.obsolete ?? []) {
|
|
385
|
+
lines.push(`! ${item.message} ${item.command}`);
|
|
386
|
+
}
|
|
387
|
+
return lines.join('\n') + '\n';
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Reads one line, and treats anything but "s" (including no answer at all) as no. The answer comes from the
|
|
391
|
+
// `line` event, which always precedes `close`, so an input that ends right after the answer is not lost.
|
|
392
|
+
async function askYesNo(input, output) {
|
|
393
|
+
const rl = readline.createInterface({ input, ...(output?.isTTY && { output }) });
|
|
394
|
+
try {
|
|
395
|
+
const answer = await new Promise((resolve) => {
|
|
396
|
+
let answered = false;
|
|
397
|
+
rl.once('line', (line) => {
|
|
398
|
+
answered = true;
|
|
399
|
+
resolve(line);
|
|
400
|
+
});
|
|
401
|
+
rl.once('close', () => {
|
|
402
|
+
if (!answered) resolve('');
|
|
403
|
+
});
|
|
404
|
+
});
|
|
405
|
+
return /^s(í|i)?$/iu.test(answer.trim());
|
|
406
|
+
} finally {
|
|
407
|
+
rl.close();
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
async function runRepair(rest, { out, err, input, output, isTTY, options }) {
|
|
412
|
+
const parsed = { agent: 'all' };
|
|
413
|
+
const seen = new Set();
|
|
414
|
+
for (let i = 0; i < rest.length; i++) {
|
|
415
|
+
const flag = rest[i];
|
|
416
|
+
if (flag === '--si') {
|
|
417
|
+
await err('reparar siempre pregunta; no admite --si.\n');
|
|
418
|
+
return 2;
|
|
419
|
+
}
|
|
420
|
+
if (seen.has(flag)) {
|
|
421
|
+
await err(HELP);
|
|
422
|
+
return 2;
|
|
423
|
+
}
|
|
424
|
+
seen.add(flag);
|
|
425
|
+
if (flag === '--desde' && rest[i + 1]) parsed.from = rest[++i];
|
|
426
|
+
else if (flag === '--agent' && ['codex', 'claude', 'antigravity', 'all'].includes(rest[i + 1]))
|
|
427
|
+
parsed.agent = rest[++i];
|
|
428
|
+
else {
|
|
429
|
+
await err(HELP);
|
|
430
|
+
return 2;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
if (!parsed.from) {
|
|
434
|
+
await err(HELP);
|
|
435
|
+
return 2;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
const home = options.homeDirectory ?? os.homedir();
|
|
439
|
+
const short = (value) =>
|
|
440
|
+
value === home || value.startsWith(home + path.sep) ? `~${value.slice(home.length)}` : value;
|
|
441
|
+
try {
|
|
442
|
+
const plan = await planLegacyRepair({ ...options, ...parsed });
|
|
443
|
+
const from = short(plan.from);
|
|
444
|
+
if (plan.links.length === 0) {
|
|
445
|
+
await out(`No encontré enlaces rotos hacia ${from}.\n`);
|
|
446
|
+
return 0;
|
|
447
|
+
}
|
|
448
|
+
await out(
|
|
449
|
+
[
|
|
450
|
+
`Encontré ${plural(plan.links.length, 'enlace roto', 'enlaces rotos')} hacia ${from}.`,
|
|
451
|
+
'No puedo comprobar que sean de nexakit: revisa la lista.',
|
|
452
|
+
...plan.links.map((link) => ` ${link.client} · ${link.name}`),
|
|
453
|
+
'',
|
|
454
|
+
].join('\n'),
|
|
455
|
+
);
|
|
456
|
+
const interactive = Boolean(isTTY ?? (input?.isTTY && output?.isTTY));
|
|
457
|
+
if (!interactive) {
|
|
458
|
+
await err('Este comando necesita una terminal interactiva.\n');
|
|
459
|
+
return 2;
|
|
460
|
+
}
|
|
461
|
+
await out('¿Los re-apunto a la copia de nexakit en uso? [s/N]\n');
|
|
462
|
+
if (!(await askYesNo(input, output))) {
|
|
463
|
+
await out('No se cambió nada.\n');
|
|
464
|
+
return 0;
|
|
465
|
+
}
|
|
466
|
+
const result = await applyLegacyRepair(plan, options);
|
|
467
|
+
const lines = [`Listo: ${plural(result.repaired, 'enlace re-apuntado', 'enlaces re-apuntados')}.`];
|
|
468
|
+
if (result.changed)
|
|
469
|
+
lines.push(
|
|
470
|
+
`${plural(result.changed, 'enlace cambió', 'enlaces cambiaron')} mientras confirmabas y no se tocó; vuelve a ejecutar el comando.`,
|
|
471
|
+
);
|
|
472
|
+
if (result.skipped.length)
|
|
473
|
+
lines.push(`No se repararon ${result.skipped.join(', ')}: la copia en uso no las incluye.`);
|
|
474
|
+
if (result.mcpCommands.length) {
|
|
475
|
+
lines.push(
|
|
476
|
+
'El MCP de revisión cruzada también apunta ahí. Para moverlo ejecuta:',
|
|
477
|
+
...result.mcpCommands.map((command) => ` ${command}`),
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
if (result.repaired) lines.push('Reinicia tus agentes para que carguen las skills.');
|
|
481
|
+
await out(lines.join('\n') + '\n');
|
|
482
|
+
return result.changed || result.skipped.length ? 1 : 0;
|
|
212
483
|
} catch {
|
|
213
|
-
await err('No se pudo
|
|
484
|
+
await err('No se pudo comprobar ni reparar. Revisa rutas y permisos; no se reemplazan destinos.\n');
|
|
485
|
+
return 1;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// Options tests and callers pass (homeDirectory, toolkitRoot, codexHome, environment, runCommand, runNpm, ...).
|
|
490
|
+
function setupStateOptions(projectRoot, options) {
|
|
491
|
+
const { runNpm, packageSpec, acquire, ...rest } = options;
|
|
492
|
+
return {
|
|
493
|
+
stateOptions: { ...rest, start: projectRoot },
|
|
494
|
+
applyOptions: {
|
|
495
|
+
...(runNpm && { runNpm }),
|
|
496
|
+
...(packageSpec && { packageSpec }),
|
|
497
|
+
...(acquire && { acquire }),
|
|
498
|
+
},
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
async function runSetup(parsed, { out, err, input, output, isTTY, options }) {
|
|
503
|
+
const { stateOptions, applyOptions } = setupStateOptions(parsed.projectRoot, options);
|
|
504
|
+
const interactive = Boolean(isTTY ?? (input?.isTTY && output?.isTTY));
|
|
505
|
+
try {
|
|
506
|
+
if (interactive && !parsed.yes)
|
|
507
|
+
return await runSetupTui({ input, output, options: stateOptions, applyOptions });
|
|
508
|
+
const state = await gatherSetupState(stateOptions);
|
|
509
|
+
const answers = suggestedAnswers(state);
|
|
510
|
+
const confirmation = buildConfirmation(state, answers);
|
|
511
|
+
const title = `nexakit · ${state.project.displayName}${state.project.fromSubfolder ? ' (repositorio completo)' : ''}`;
|
|
512
|
+
if (confirmation.allReady) {
|
|
513
|
+
await out(
|
|
514
|
+
`${title}\nTodo está listo.\n${confirmation.lines.map((line) => ` ${line.mark} ${line.text}`).join('\n')}\n`,
|
|
515
|
+
);
|
|
516
|
+
return 0;
|
|
517
|
+
}
|
|
518
|
+
if (!parsed.yes) {
|
|
519
|
+
await out(
|
|
520
|
+
`${title}\nCon las respuestas sugeridas prepararía:\n${confirmation.lines.map((line) => ` ${line.mark} ${line.text}`).join('\n')}\n\n` +
|
|
521
|
+
'No hay una terminal interactiva para preguntarte.\nPara aplicarlo sin preguntas: npx nexakit --si\n',
|
|
522
|
+
);
|
|
523
|
+
return 2;
|
|
524
|
+
}
|
|
525
|
+
await out(`${title}\n`);
|
|
526
|
+
const labels = Object.fromEntries(confirmation.blocks.map((block) => [block.id, block.label]));
|
|
527
|
+
const result = await applySetup(stateOptions, answers, {
|
|
528
|
+
...applyOptions,
|
|
529
|
+
onProgress: async (id, phase, message) => {
|
|
530
|
+
// The final check prints its own line with the result, below.
|
|
531
|
+
if (phase === 'done' && id !== 'final-check') await out(` ◇ ${labels[id] ?? id}\n`);
|
|
532
|
+
if (phase === 'failed') await err(` ✗ ${labels[id] ?? id}: ${message}\n`);
|
|
533
|
+
},
|
|
534
|
+
});
|
|
535
|
+
if (result.finalCheck)
|
|
536
|
+
await out(` ${result.finalCheck.ok ? '◇' : '!'} Comprobación final: ${result.finalCheck.message}\n`);
|
|
537
|
+
if (result.ok) await out('Listo. Cierra y abre tu IA para que cargue las skills y la memoria.\n');
|
|
538
|
+
return result.ok ? 0 : 1;
|
|
539
|
+
} catch (error) {
|
|
540
|
+
await err(`No se pudo preparar el proyecto: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
214
541
|
return 1;
|
|
215
542
|
}
|
|
216
543
|
}
|