fwinit 1.4.5 → 1.4.6
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/README.md +15 -0
- package/dist/index.js +348 -33
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -35,6 +35,20 @@ fwinit fullstack mi-proyecto -b fastify -f react-native -p pnpm
|
|
|
35
35
|
# Ver templates disponibles (agrupados por capa)
|
|
36
36
|
fwinit list
|
|
37
37
|
|
|
38
|
+
# Instalar skills globalmente (OpenCode, Pi, Agent Skills) — interactivo
|
|
39
|
+
fwinit skills
|
|
40
|
+
|
|
41
|
+
# Instalar skills globalmente sin preguntas (modo scripting/CI)
|
|
42
|
+
fwinit skills --global opencode,pi # o: --global all
|
|
43
|
+
|
|
44
|
+
# Instalar solo algunas skills
|
|
45
|
+
fwinit skills --global all --only create-specs,design
|
|
46
|
+
|
|
47
|
+
# Eliminar skills globales (mismos flags; interactivo pregunta qué eliminar)
|
|
48
|
+
fwinit skills --remove
|
|
49
|
+
fwinit skills --global all --remove
|
|
50
|
+
fwinit skills --global pi --remove --only design
|
|
51
|
+
|
|
38
52
|
# Ayuda
|
|
39
53
|
fwinit --help
|
|
40
54
|
```
|
|
@@ -48,6 +62,7 @@ fwinit --help
|
|
|
48
62
|
> Full stack (backend + frontend)
|
|
49
63
|
Solo backend (API)
|
|
50
64
|
Solo frontend (app)
|
|
65
|
+
Skills globales (instalar / eliminar)
|
|
51
66
|
```
|
|
52
67
|
|
|
53
68
|
- **Full stack**: te pregunta primero el backend y después el frontend, y arma el monorepo completo.
|
package/dist/index.js
CHANGED
|
@@ -5,8 +5,8 @@ import { Command } from "commander";
|
|
|
5
5
|
import ora from "ora";
|
|
6
6
|
import chalk from "chalk";
|
|
7
7
|
import { readFileSync } from "fs";
|
|
8
|
-
import { mkdir as
|
|
9
|
-
import { join as
|
|
8
|
+
import { mkdir as mkdir3, cp as cp3, readFile as readFile2 } from "fs/promises";
|
|
9
|
+
import { join as join3, basename } from "path";
|
|
10
10
|
|
|
11
11
|
// src/templates.ts
|
|
12
12
|
var TEMPLATES = [
|
|
@@ -266,33 +266,33 @@ async function exists(path) {
|
|
|
266
266
|
}
|
|
267
267
|
async function listMarkdown(dir) {
|
|
268
268
|
try {
|
|
269
|
-
const { readdir:
|
|
270
|
-
return (await
|
|
269
|
+
const { readdir: readdir3 } = await import("fs/promises");
|
|
270
|
+
return (await readdir3(dir)).filter((f) => f.endsWith(".md"));
|
|
271
271
|
} catch {
|
|
272
272
|
return [];
|
|
273
273
|
}
|
|
274
274
|
}
|
|
275
275
|
async function planAgentBundle(repoRoot, bundle = AGENT_BUNDLE) {
|
|
276
|
-
const { join:
|
|
276
|
+
const { join: join4 } = await import("path");
|
|
277
277
|
const installs = [];
|
|
278
278
|
const skillDirs = [];
|
|
279
279
|
const missingRequired = [];
|
|
280
280
|
for (const entry of bundle) {
|
|
281
|
-
const src =
|
|
281
|
+
const src = join4(repoRoot, "skills", entry.dir);
|
|
282
282
|
if (entry.kind === "library") {
|
|
283
283
|
if (!await exists(src)) {
|
|
284
284
|
if (entry.required) missingRequired.push(entry.dir);
|
|
285
285
|
continue;
|
|
286
286
|
}
|
|
287
287
|
installs.push({ from: src, to: `skills/${entry.dir}`, recursive: true });
|
|
288
|
-
const cmds =
|
|
288
|
+
const cmds = join4(src, "commands");
|
|
289
289
|
for (const file of await listMarkdown(cmds)) {
|
|
290
|
-
installs.push({ from:
|
|
290
|
+
installs.push({ from: join4(cmds, file), to: `commands/${file}` });
|
|
291
291
|
}
|
|
292
292
|
continue;
|
|
293
293
|
}
|
|
294
|
-
const skill =
|
|
295
|
-
const command =
|
|
294
|
+
const skill = join4(src, "SKILL.md");
|
|
295
|
+
const command = join4(src, "command.md");
|
|
296
296
|
if (!await exists(skill)) {
|
|
297
297
|
if (entry.required) missingRequired.push(entry.dir);
|
|
298
298
|
continue;
|
|
@@ -305,7 +305,7 @@ async function planAgentBundle(repoRoot, bundle = AGENT_BUNDLE) {
|
|
|
305
305
|
missingRequired.push(`${entry.dir}/command.md`);
|
|
306
306
|
}
|
|
307
307
|
for (const extra of entry.extras ?? []) {
|
|
308
|
-
const from =
|
|
308
|
+
const from = join4(src, extra);
|
|
309
309
|
if (await exists(from)) {
|
|
310
310
|
installs.push({
|
|
311
311
|
from,
|
|
@@ -329,7 +329,8 @@ async function selectScope() {
|
|
|
329
329
|
choices: [
|
|
330
330
|
{ name: "Full stack (backend + frontend)", value: "fullstack" },
|
|
331
331
|
{ name: "Solo backend (API)", value: "backend" },
|
|
332
|
-
{ name: "Solo frontend (app)", value: "frontend" }
|
|
332
|
+
{ name: "Solo frontend (app)", value: "frontend" },
|
|
333
|
+
{ name: "Skills globales (instalar / eliminar)", value: "skills" }
|
|
333
334
|
]
|
|
334
335
|
}
|
|
335
336
|
]);
|
|
@@ -404,6 +405,179 @@ async function askLayerLayout(layer) {
|
|
|
404
405
|
]);
|
|
405
406
|
return layout;
|
|
406
407
|
}
|
|
408
|
+
async function selectGlobalTargets(choices) {
|
|
409
|
+
const { targets } = await inquirer.prompt([
|
|
410
|
+
{
|
|
411
|
+
type: "checkbox",
|
|
412
|
+
name: "targets",
|
|
413
|
+
message: "\xBFD\xF3nde quer\xE9s instalar las skills?",
|
|
414
|
+
choices
|
|
415
|
+
}
|
|
416
|
+
]);
|
|
417
|
+
return targets;
|
|
418
|
+
}
|
|
419
|
+
async function selectGlobalTargetsToRemove(choices) {
|
|
420
|
+
const { targets } = await inquirer.prompt([
|
|
421
|
+
{
|
|
422
|
+
type: "checkbox",
|
|
423
|
+
name: "targets",
|
|
424
|
+
message: "\xBFDe d\xF3nde quer\xE9s eliminar las skills?",
|
|
425
|
+
choices
|
|
426
|
+
}
|
|
427
|
+
]);
|
|
428
|
+
return targets;
|
|
429
|
+
}
|
|
430
|
+
async function selectSkillsAction() {
|
|
431
|
+
const { action } = await inquirer.prompt([
|
|
432
|
+
{
|
|
433
|
+
type: "list",
|
|
434
|
+
name: "action",
|
|
435
|
+
message: "\xBFQu\xE9 quer\xE9s hacer con las skills globales?",
|
|
436
|
+
choices: [
|
|
437
|
+
{ name: "Instalar / actualizar", value: "install" },
|
|
438
|
+
{ name: "Eliminar", value: "remove" }
|
|
439
|
+
]
|
|
440
|
+
}
|
|
441
|
+
]);
|
|
442
|
+
return action;
|
|
443
|
+
}
|
|
444
|
+
async function askGlobalSkillsToo() {
|
|
445
|
+
const { global } = await inquirer.prompt([
|
|
446
|
+
{
|
|
447
|
+
type: "confirm",
|
|
448
|
+
name: "global",
|
|
449
|
+
message: "\xBFInstalar las skills tambi\xE9n globalmente (OpenCode/Pi/Agent Skills)?",
|
|
450
|
+
default: false
|
|
451
|
+
}
|
|
452
|
+
]);
|
|
453
|
+
return global;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// src/global-skills.ts
|
|
457
|
+
import { mkdir as mkdir2, cp as cp2, rm as rm2, readdir as readdir2 } from "fs/promises";
|
|
458
|
+
import { join as join2 } from "path";
|
|
459
|
+
function resolveGlobalTargets() {
|
|
460
|
+
const home = process.env.HOME;
|
|
461
|
+
if (!home) return [];
|
|
462
|
+
return [
|
|
463
|
+
{
|
|
464
|
+
id: "opencode",
|
|
465
|
+
label: `OpenCode global (${home}/.config/opencode)`,
|
|
466
|
+
root: join2(home, ".config", "opencode"),
|
|
467
|
+
skillsDir: "skills",
|
|
468
|
+
commandsDir: "command"
|
|
469
|
+
},
|
|
470
|
+
{
|
|
471
|
+
id: "pi",
|
|
472
|
+
label: `Pi global (${home}/.pi/agent)`,
|
|
473
|
+
root: join2(home, ".pi", "agent"),
|
|
474
|
+
skillsDir: "skills",
|
|
475
|
+
commandsDir: "prompts"
|
|
476
|
+
},
|
|
477
|
+
{
|
|
478
|
+
id: "agents",
|
|
479
|
+
label: `Agent Skills global (${home}/.agents/skills)`,
|
|
480
|
+
root: join2(home, ".agents"),
|
|
481
|
+
skillsDir: "skills"
|
|
482
|
+
}
|
|
483
|
+
];
|
|
484
|
+
}
|
|
485
|
+
function filterBundleByNames(only) {
|
|
486
|
+
const set = new Set(only);
|
|
487
|
+
const bundle = AGENT_BUNDLE.filter((e) => set.has(e.dir));
|
|
488
|
+
const known = new Set(AGENT_BUNDLE.map((e) => e.dir));
|
|
489
|
+
return { bundle, unknown: only.filter((n) => !known.has(n)) };
|
|
490
|
+
}
|
|
491
|
+
function mapPlanToTarget(plan, target) {
|
|
492
|
+
const mapped = [];
|
|
493
|
+
for (const item of plan.installs) {
|
|
494
|
+
if (item.to.startsWith("skills/")) {
|
|
495
|
+
mapped.push({
|
|
496
|
+
from: item.from,
|
|
497
|
+
to: join2(target.root, target.skillsDir, item.to.slice("skills/".length)),
|
|
498
|
+
recursive: item.recursive
|
|
499
|
+
});
|
|
500
|
+
continue;
|
|
501
|
+
}
|
|
502
|
+
if (item.to.startsWith("commands/")) {
|
|
503
|
+
if (!target.commandsDir) continue;
|
|
504
|
+
mapped.push({
|
|
505
|
+
from: item.from,
|
|
506
|
+
to: join2(target.root, target.commandsDir, item.to.slice("commands/".length)),
|
|
507
|
+
recursive: item.recursive
|
|
508
|
+
});
|
|
509
|
+
continue;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
return mapped;
|
|
513
|
+
}
|
|
514
|
+
async function installGlobalSkills(repoRoot, targets, bundle = AGENT_BUNDLE) {
|
|
515
|
+
const plan = await planAgentBundle(repoRoot, bundle);
|
|
516
|
+
const results = [];
|
|
517
|
+
for (const target of targets) {
|
|
518
|
+
try {
|
|
519
|
+
let copied = 0;
|
|
520
|
+
for (const item of mapPlanToTarget(plan, target)) {
|
|
521
|
+
if (item.recursive === true) {
|
|
522
|
+
await rm2(item.to, { recursive: true, force: true });
|
|
523
|
+
}
|
|
524
|
+
await mkdir2(
|
|
525
|
+
item.recursive === true ? item.to : join2(item.to, ".."),
|
|
526
|
+
{ recursive: true }
|
|
527
|
+
);
|
|
528
|
+
await cp2(item.from, item.to, {
|
|
529
|
+
recursive: item.recursive === true
|
|
530
|
+
});
|
|
531
|
+
copied++;
|
|
532
|
+
}
|
|
533
|
+
results.push({ target, copied });
|
|
534
|
+
} catch (error) {
|
|
535
|
+
results.push({
|
|
536
|
+
target,
|
|
537
|
+
copied: 0,
|
|
538
|
+
error: error.message
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
return results;
|
|
543
|
+
}
|
|
544
|
+
async function uninstallGlobalSkills(targets, bundle = AGENT_BUNDLE) {
|
|
545
|
+
const skillNames = bundle.map((e) => e.dir);
|
|
546
|
+
const results = [];
|
|
547
|
+
for (const target of targets) {
|
|
548
|
+
try {
|
|
549
|
+
const removed = [];
|
|
550
|
+
for (const name of skillNames) {
|
|
551
|
+
await rm2(join2(target.root, target.skillsDir, name), {
|
|
552
|
+
recursive: true,
|
|
553
|
+
force: true
|
|
554
|
+
});
|
|
555
|
+
removed.push(`${target.skillsDir}/${name}`);
|
|
556
|
+
}
|
|
557
|
+
if (target.commandsDir) {
|
|
558
|
+
const cmdDir = join2(target.root, target.commandsDir);
|
|
559
|
+
for (const name of skillNames) {
|
|
560
|
+
await rm2(join2(cmdDir, `${name}.md`), { force: true });
|
|
561
|
+
removed.push(`${target.commandsDir}/${name}.md`);
|
|
562
|
+
try {
|
|
563
|
+
const prefix = `${name}-`;
|
|
564
|
+
for (const f of await readdir2(cmdDir)) {
|
|
565
|
+
if (f.startsWith(prefix) && f.endsWith(".md")) {
|
|
566
|
+
await rm2(join2(cmdDir, f), { force: true });
|
|
567
|
+
removed.push(`${target.commandsDir}/${f}`);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
} catch {
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
results.push({ target, removed });
|
|
575
|
+
} catch (error) {
|
|
576
|
+
results.push({ target, removed: [], error: error.message });
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
return results;
|
|
580
|
+
}
|
|
407
581
|
|
|
408
582
|
// src/utils.ts
|
|
409
583
|
import { existsSync } from "fs";
|
|
@@ -507,9 +681,9 @@ function adaptScriptToNode(script) {
|
|
|
507
681
|
return s;
|
|
508
682
|
}
|
|
509
683
|
async function adaptToNodeRuntime(projectPath) {
|
|
510
|
-
const { readFile: readFile3, writeFile: writeFile2, readdir:
|
|
511
|
-
const { join:
|
|
512
|
-
const pkgPath =
|
|
684
|
+
const { readFile: readFile3, writeFile: writeFile2, readdir: readdir3 } = await import("fs/promises");
|
|
685
|
+
const { join: join4 } = await import("path");
|
|
686
|
+
const pkgPath = join4(projectPath, "package.json");
|
|
513
687
|
const pkg = JSON.parse(await readFile3(pkgPath, "utf-8"));
|
|
514
688
|
for (const key of Object.keys(pkg.scripts ?? {})) {
|
|
515
689
|
pkg.scripts[key] = adaptScriptToNode(pkg.scripts[key]);
|
|
@@ -520,9 +694,9 @@ async function adaptToNodeRuntime(projectPath) {
|
|
|
520
694
|
delete pkg.devDependencies["bun-types"];
|
|
521
695
|
await writeFile2(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
522
696
|
async function walk(dir) {
|
|
523
|
-
const entries = await
|
|
697
|
+
const entries = await readdir3(dir, { withFileTypes: true });
|
|
524
698
|
for (const entry of entries) {
|
|
525
|
-
const full =
|
|
699
|
+
const full = join4(dir, entry.name);
|
|
526
700
|
if (entry.isDirectory()) {
|
|
527
701
|
if (entry.name !== "node_modules") await walk(full);
|
|
528
702
|
} else if (entry.name.endsWith(".test.ts")) {
|
|
@@ -533,12 +707,12 @@ async function adaptToNodeRuntime(projectPath) {
|
|
|
533
707
|
}
|
|
534
708
|
}
|
|
535
709
|
}
|
|
536
|
-
const srcDir =
|
|
710
|
+
const srcDir = join4(projectPath, "src");
|
|
537
711
|
try {
|
|
538
712
|
await walk(srcDir);
|
|
539
713
|
} catch {
|
|
540
714
|
}
|
|
541
|
-
const tsconfigPath =
|
|
715
|
+
const tsconfigPath = join4(projectPath, "tsconfig.json");
|
|
542
716
|
try {
|
|
543
717
|
const tsconfig = JSON.parse(await readFile3(tsconfigPath, "utf-8"));
|
|
544
718
|
if (tsconfig.compilerOptions?.types?.includes("bun-types")) {
|
|
@@ -550,8 +724,8 @@ async function adaptToNodeRuntime(projectPath) {
|
|
|
550
724
|
}
|
|
551
725
|
async function writeRootGitignore(projectPath) {
|
|
552
726
|
const { writeFile: writeFile2, access } = await import("fs/promises");
|
|
553
|
-
const { join:
|
|
554
|
-
const gitignorePath =
|
|
727
|
+
const { join: join4 } = await import("path");
|
|
728
|
+
const gitignorePath = join4(projectPath, ".gitignore");
|
|
555
729
|
try {
|
|
556
730
|
await access(gitignorePath);
|
|
557
731
|
} catch {
|
|
@@ -579,7 +753,7 @@ async function gitInit(projectPath) {
|
|
|
579
753
|
}
|
|
580
754
|
async function writeRootFiles(info) {
|
|
581
755
|
const { writeFile: writeFile2 } = await import("fs/promises");
|
|
582
|
-
const { join:
|
|
756
|
+
const { join: join4 } = await import("path");
|
|
583
757
|
const projectPath = resolve(process.cwd(), info.projectName);
|
|
584
758
|
await writeRootGitignore(projectPath);
|
|
585
759
|
const installB = info.backend.postInit?.install ? resolveCommand(info.backend.postInit.install, info.pm) : `${info.pm} install`;
|
|
@@ -615,7 +789,7 @@ async function writeRootFiles(info) {
|
|
|
615
789
|
"```",
|
|
616
790
|
""
|
|
617
791
|
].join("\n");
|
|
618
|
-
await writeFile2(
|
|
792
|
+
await writeFile2(join4(projectPath, "README.md"), readme);
|
|
619
793
|
}
|
|
620
794
|
|
|
621
795
|
// src/index.ts
|
|
@@ -634,15 +808,15 @@ var AGENT_DIRS = [".opencode", ".pi", ".agents"];
|
|
|
634
808
|
async function installAgentSkills(repoRoot, projectPath) {
|
|
635
809
|
const plan = await planAgentBundle(repoRoot);
|
|
636
810
|
for (const agentDir of AGENT_DIRS) {
|
|
637
|
-
const root =
|
|
811
|
+
const root = join3(projectPath, agentDir);
|
|
638
812
|
for (const dir of plan.skillDirs) {
|
|
639
|
-
await
|
|
813
|
+
await mkdir3(join3(root, dir), { recursive: true });
|
|
640
814
|
}
|
|
641
815
|
for (const dir of plan.commandDirs) {
|
|
642
|
-
await
|
|
816
|
+
await mkdir3(join3(root, dir), { recursive: true });
|
|
643
817
|
}
|
|
644
818
|
for (const item of plan.installs) {
|
|
645
|
-
await
|
|
819
|
+
await cp3(item.from, join3(root, item.to), {
|
|
646
820
|
recursive: item.recursive === true
|
|
647
821
|
});
|
|
648
822
|
}
|
|
@@ -656,10 +830,29 @@ async function installAgentSkills(repoRoot, projectPath) {
|
|
|
656
830
|
);
|
|
657
831
|
}
|
|
658
832
|
}
|
|
833
|
+
async function installGlobalSkillsWithSpinner(targets, bundle) {
|
|
834
|
+
const spinner = ora("Descargando bundle de skills...").start();
|
|
835
|
+
const { tempDir, repoRoot } = await downloadAndExtractRepo();
|
|
836
|
+
try {
|
|
837
|
+
spinner.text = "Instalando skills...";
|
|
838
|
+
const results = await installGlobalSkills(repoRoot, targets, bundle);
|
|
839
|
+
spinner.succeed("Skills globales instaladas");
|
|
840
|
+
for (const r of results) {
|
|
841
|
+
if (r.error) {
|
|
842
|
+
console.log(chalk.red(` \u2716 ${r.target.label}: ${r.error}`));
|
|
843
|
+
} else {
|
|
844
|
+
console.log(chalk.dim(` \u2714 ${r.target.label} \u2014 ${r.copied} archivos`));
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
console.log();
|
|
848
|
+
} finally {
|
|
849
|
+
await cleanup(tempDir);
|
|
850
|
+
}
|
|
851
|
+
}
|
|
659
852
|
async function readTemplatePostInit(dir) {
|
|
660
853
|
try {
|
|
661
854
|
const tj = JSON.parse(
|
|
662
|
-
await readFile2(
|
|
855
|
+
await readFile2(join3(dir, "template.json"), "utf-8")
|
|
663
856
|
);
|
|
664
857
|
return tj.postInit;
|
|
665
858
|
} catch {
|
|
@@ -669,14 +862,14 @@ async function readTemplatePostInit(dir) {
|
|
|
669
862
|
async function createFullStackProject(projectPath, backendTpl, frontendTpl, pm) {
|
|
670
863
|
const { tempDir, repoRoot } = await downloadAndExtractRepo();
|
|
671
864
|
const projectName = basename(projectPath);
|
|
672
|
-
const backendPath =
|
|
673
|
-
const frontendPath =
|
|
865
|
+
const backendPath = join3(projectPath, "backend");
|
|
866
|
+
const frontendPath = join3(projectPath, "frontend");
|
|
674
867
|
await copyTemplate(
|
|
675
|
-
|
|
868
|
+
join3(repoRoot, "templates", backendTpl.folder),
|
|
676
869
|
backendPath
|
|
677
870
|
);
|
|
678
871
|
await copyTemplate(
|
|
679
|
-
|
|
872
|
+
join3(repoRoot, "templates", frontendTpl.folder),
|
|
680
873
|
frontendPath
|
|
681
874
|
);
|
|
682
875
|
await substituteTemplate(backendPath, projectName, backendTpl.folder);
|
|
@@ -735,6 +928,102 @@ program.command("list").description("Mostrar templates disponibles").action(() =
|
|
|
735
928
|
console.log();
|
|
736
929
|
}
|
|
737
930
|
});
|
|
931
|
+
async function runSkillsFlow(opts = {}) {
|
|
932
|
+
const all = resolveGlobalTargets();
|
|
933
|
+
if (all.length === 0) {
|
|
934
|
+
console.error(chalk.red("\n\u2716 No se pudo resolver el HOME del usuario\n"));
|
|
935
|
+
process.exit(1);
|
|
936
|
+
}
|
|
937
|
+
console.log(chalk.bold("\nSkills disponibles:\n"));
|
|
938
|
+
console.log(chalk.dim(" create-specs \u2014 specs para proyecto nuevo (/create-specs)"));
|
|
939
|
+
console.log(chalk.dim(" create-specs-from-code \u2014 specs desde c\xF3digo existente (/create-specs-from-code)"));
|
|
940
|
+
console.log(chalk.dim(" design/ \u2014 dark-luxury, glassmorphism, minimal-dashboard, minimal-light, neo-brutalist (/design-<estilo>)\n"));
|
|
941
|
+
let bundle;
|
|
942
|
+
if (opts.only) {
|
|
943
|
+
const names = opts.only.split(",").map((s) => s.trim()).filter(Boolean);
|
|
944
|
+
const { bundle: filtered, unknown } = filterBundleByNames(names);
|
|
945
|
+
if (unknown.length > 0) {
|
|
946
|
+
console.log(chalk.yellow(` \u26A0 Skills desconocidas (ignoradas): ${unknown.join(", ")}
|
|
947
|
+
`));
|
|
948
|
+
}
|
|
949
|
+
if (filtered.length === 0) {
|
|
950
|
+
console.log(chalk.dim("\n Ninguna skill v\xE1lida en --only \u2014 no se hizo nada.\n"));
|
|
951
|
+
return false;
|
|
952
|
+
}
|
|
953
|
+
bundle = filtered;
|
|
954
|
+
}
|
|
955
|
+
const remove = opts.remove === true;
|
|
956
|
+
const interactive = !opts.global;
|
|
957
|
+
let action = remove ? "remove" : "install";
|
|
958
|
+
if (interactive && !remove) {
|
|
959
|
+
action = await selectSkillsAction();
|
|
960
|
+
}
|
|
961
|
+
let targets;
|
|
962
|
+
if (opts.global) {
|
|
963
|
+
const ids = opts.global.split(",").map((s) => s.trim().toLowerCase());
|
|
964
|
+
if (ids.includes("all")) {
|
|
965
|
+
targets = all;
|
|
966
|
+
} else {
|
|
967
|
+
const valid = new Map(all.map((t) => [t.id, t]));
|
|
968
|
+
targets = [];
|
|
969
|
+
for (const id of ids) {
|
|
970
|
+
const t = valid.get(id);
|
|
971
|
+
if (!t) {
|
|
972
|
+
console.error(
|
|
973
|
+
chalk.red(`
|
|
974
|
+
\u2716 Destino "${id}" inv\xE1lido. Us\xE1: ${all.map((x) => x.id).join(", ")} o all
|
|
975
|
+
`)
|
|
976
|
+
);
|
|
977
|
+
process.exit(1);
|
|
978
|
+
}
|
|
979
|
+
targets.push(t);
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
} else {
|
|
983
|
+
const pick = action === "remove" ? selectGlobalTargetsToRemove : selectGlobalTargets;
|
|
984
|
+
const selected = await pick(all.map((t) => ({ name: t.label, value: t.id })));
|
|
985
|
+
if (selected.length === 0) {
|
|
986
|
+
console.log(
|
|
987
|
+
chalk.dim(`
|
|
988
|
+
Nada seleccionado \u2014 no se ${action === "remove" ? "elimin\xF3" : "instal\xF3"} nada.
|
|
989
|
+
`)
|
|
990
|
+
);
|
|
991
|
+
return false;
|
|
992
|
+
}
|
|
993
|
+
targets = all.filter((t) => selected.includes(t.id));
|
|
994
|
+
}
|
|
995
|
+
if (action === "remove") {
|
|
996
|
+
const results = await uninstallGlobalSkills(targets, bundle);
|
|
997
|
+
for (const r of results) {
|
|
998
|
+
console.log(chalk.dim(` \u2714 ${r.target.label} \u2014 ${r.removed.length} eliminados`));
|
|
999
|
+
}
|
|
1000
|
+
console.log();
|
|
1001
|
+
return true;
|
|
1002
|
+
}
|
|
1003
|
+
await installGlobalSkillsWithSpinner(targets, bundle);
|
|
1004
|
+
return true;
|
|
1005
|
+
}
|
|
1006
|
+
program.command("skills").description(
|
|
1007
|
+
"Instalar o eliminar las skills del repo en ubicaciones globales (OpenCode, Pi, Agent Skills)"
|
|
1008
|
+
).option(
|
|
1009
|
+
"-g, --global <targets>",
|
|
1010
|
+
"Destinos separados por coma: opencode, pi, agents (o 'all'). Saltea la pregunta"
|
|
1011
|
+
).option(
|
|
1012
|
+
"-o, --only <skills>",
|
|
1013
|
+
"Solo estas skills, separadas por coma: create-specs, create-specs-from-code, design"
|
|
1014
|
+
).option(
|
|
1015
|
+
"-r, --remove",
|
|
1016
|
+
"Eliminar en vez de instalar (combinar con --global y opcionalmente --only)"
|
|
1017
|
+
).action(async (opts) => {
|
|
1018
|
+
try {
|
|
1019
|
+
await runSkillsFlow(opts);
|
|
1020
|
+
} catch (error) {
|
|
1021
|
+
console.error(chalk.red(`
|
|
1022
|
+
\u2716 Error: ${error.message}
|
|
1023
|
+
`));
|
|
1024
|
+
process.exit(1);
|
|
1025
|
+
}
|
|
1026
|
+
});
|
|
738
1027
|
program.argument("[template]", "Template a usar (o 'fullstack' para backend + frontend)").argument("[project-name]", "Nombre del proyecto").option("-p, --pm <package-manager>", "Package manager a usar: npm, pnpm o bun").option("--backend", "Empaquetar el c\xF3digo en backend/ (saltea la pregunta)").option("--no-backend", "Dejar el c\xF3digo en la ra\xEDz del proyecto (saltea la pregunta)").option("-b, --backend-template <tpl>", "Backend a usar en fullstack (saltea la pregunta)").option("-f, --frontend-template <tpl>", "Frontend a usar en fullstack (saltea la pregunta)").option("--git", "Inicializar un repositorio git (saltea la pregunta)").option("--no-git", "No inicializar git (saltea la pregunta)").action(
|
|
739
1028
|
async (templateArg, projectNameArg) => {
|
|
740
1029
|
try {
|
|
@@ -756,6 +1045,10 @@ program.argument("[template]", "Template a usar (o 'fullstack' para backend + fr
|
|
|
756
1045
|
const rawArg = templateArg?.toLowerCase();
|
|
757
1046
|
if (rawArg === void 0) {
|
|
758
1047
|
const scope = await selectScope();
|
|
1048
|
+
if (scope === "skills") {
|
|
1049
|
+
await runSkillsFlow();
|
|
1050
|
+
return;
|
|
1051
|
+
}
|
|
759
1052
|
if (scope === "fullstack") {
|
|
760
1053
|
isFullStack = true;
|
|
761
1054
|
} else {
|
|
@@ -853,7 +1146,7 @@ program.argument("[template]", "Template a usar (o 'fullstack' para backend + fr
|
|
|
853
1146
|
spinner.succeed("Template descargado");
|
|
854
1147
|
} else {
|
|
855
1148
|
const { tempDir, templatePath, repoRoot } = await downloadAndExtract(template.folder);
|
|
856
|
-
const codePath = useLayerLayout ?
|
|
1149
|
+
const codePath = useLayerLayout ? join3(projectPath, layer) : projectPath;
|
|
857
1150
|
spinner.text = "Creando proyecto...";
|
|
858
1151
|
await copyTemplate(templatePath, codePath);
|
|
859
1152
|
await substituteTemplate(codePath, projectName, template.folder);
|
|
@@ -896,6 +1189,28 @@ program.argument("[template]", "Template a usar (o 'fullstack' para backend + fr
|
|
|
896
1189
|
);
|
|
897
1190
|
}
|
|
898
1191
|
}
|
|
1192
|
+
if (git === void 0) {
|
|
1193
|
+
const wantsGlobalSkills = await askGlobalSkillsToo();
|
|
1194
|
+
if (wantsGlobalSkills) {
|
|
1195
|
+
const all = resolveGlobalTargets();
|
|
1196
|
+
if (all.length === 0) {
|
|
1197
|
+
console.log(
|
|
1198
|
+
chalk.yellow(
|
|
1199
|
+
" \u26A0 No se pudo resolver el HOME \u2014 skills globales omitidas\n"
|
|
1200
|
+
)
|
|
1201
|
+
);
|
|
1202
|
+
} else {
|
|
1203
|
+
const selected = await selectGlobalTargets(
|
|
1204
|
+
all.map((t) => ({ name: t.label, value: t.id }))
|
|
1205
|
+
);
|
|
1206
|
+
if (selected.length > 0) {
|
|
1207
|
+
await installGlobalSkillsWithSpinner(
|
|
1208
|
+
all.filter((t) => selected.includes(t.id))
|
|
1209
|
+
);
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
899
1214
|
} catch (error) {
|
|
900
1215
|
console.error(
|
|
901
1216
|
chalk.red(
|