fwinit 1.4.5 → 1.4.7

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.
Files changed (3) hide show
  1. package/README.md +15 -0
  2. package/dist/index.js +358 -34
  3. 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 mkdir2, cp as cp2, readFile as readFile2 } from "fs/promises";
9
- import { join as join2, basename } from "path";
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 = [
@@ -247,6 +247,15 @@ var AGENT_BUNDLE = [
247
247
  dir: "create-specs-from-code",
248
248
  required: true
249
249
  },
250
+ {
251
+ // Editor de prompts: detalla una idea de proyecto nuevo en
252
+ // specs/prompt.md, listo para /create-specs. Opcional a propósito —
253
+ // depende de create-specs (que sí es required) y sin ella el proyecto
254
+ // conserva /create-specs funcional. Degradar, no abortar.
255
+ kind: "skill",
256
+ dir: "create-prompt",
257
+ required: false
258
+ },
250
259
  {
251
260
  // Biblioteca de estilos: skills/design/ con una subskill por estilo y sus
252
261
  // commands (/design, /design-<estilo>) que leen specs/frontend/02-design.md.
@@ -266,33 +275,33 @@ async function exists(path) {
266
275
  }
267
276
  async function listMarkdown(dir) {
268
277
  try {
269
- const { readdir: readdir2 } = await import("fs/promises");
270
- return (await readdir2(dir)).filter((f) => f.endsWith(".md"));
278
+ const { readdir: readdir3 } = await import("fs/promises");
279
+ return (await readdir3(dir)).filter((f) => f.endsWith(".md"));
271
280
  } catch {
272
281
  return [];
273
282
  }
274
283
  }
275
284
  async function planAgentBundle(repoRoot, bundle = AGENT_BUNDLE) {
276
- const { join: join3 } = await import("path");
285
+ const { join: join4 } = await import("path");
277
286
  const installs = [];
278
287
  const skillDirs = [];
279
288
  const missingRequired = [];
280
289
  for (const entry of bundle) {
281
- const src = join3(repoRoot, "skills", entry.dir);
290
+ const src = join4(repoRoot, "skills", entry.dir);
282
291
  if (entry.kind === "library") {
283
292
  if (!await exists(src)) {
284
293
  if (entry.required) missingRequired.push(entry.dir);
285
294
  continue;
286
295
  }
287
296
  installs.push({ from: src, to: `skills/${entry.dir}`, recursive: true });
288
- const cmds = join3(src, "commands");
297
+ const cmds = join4(src, "commands");
289
298
  for (const file of await listMarkdown(cmds)) {
290
- installs.push({ from: join3(cmds, file), to: `commands/${file}` });
299
+ installs.push({ from: join4(cmds, file), to: `commands/${file}` });
291
300
  }
292
301
  continue;
293
302
  }
294
- const skill = join3(src, "SKILL.md");
295
- const command = join3(src, "command.md");
303
+ const skill = join4(src, "SKILL.md");
304
+ const command = join4(src, "command.md");
296
305
  if (!await exists(skill)) {
297
306
  if (entry.required) missingRequired.push(entry.dir);
298
307
  continue;
@@ -305,7 +314,7 @@ async function planAgentBundle(repoRoot, bundle = AGENT_BUNDLE) {
305
314
  missingRequired.push(`${entry.dir}/command.md`);
306
315
  }
307
316
  for (const extra of entry.extras ?? []) {
308
- const from = join3(src, extra);
317
+ const from = join4(src, extra);
309
318
  if (await exists(from)) {
310
319
  installs.push({
311
320
  from,
@@ -329,7 +338,8 @@ async function selectScope() {
329
338
  choices: [
330
339
  { name: "Full stack (backend + frontend)", value: "fullstack" },
331
340
  { name: "Solo backend (API)", value: "backend" },
332
- { name: "Solo frontend (app)", value: "frontend" }
341
+ { name: "Solo frontend (app)", value: "frontend" },
342
+ { name: "Skills globales (instalar / eliminar)", value: "skills" }
333
343
  ]
334
344
  }
335
345
  ]);
@@ -404,6 +414,179 @@ async function askLayerLayout(layer) {
404
414
  ]);
405
415
  return layout;
406
416
  }
417
+ async function selectGlobalTargets(choices) {
418
+ const { targets } = await inquirer.prompt([
419
+ {
420
+ type: "checkbox",
421
+ name: "targets",
422
+ message: "\xBFD\xF3nde quer\xE9s instalar las skills?",
423
+ choices
424
+ }
425
+ ]);
426
+ return targets;
427
+ }
428
+ async function selectGlobalTargetsToRemove(choices) {
429
+ const { targets } = await inquirer.prompt([
430
+ {
431
+ type: "checkbox",
432
+ name: "targets",
433
+ message: "\xBFDe d\xF3nde quer\xE9s eliminar las skills?",
434
+ choices
435
+ }
436
+ ]);
437
+ return targets;
438
+ }
439
+ async function selectSkillsAction() {
440
+ const { action } = await inquirer.prompt([
441
+ {
442
+ type: "list",
443
+ name: "action",
444
+ message: "\xBFQu\xE9 quer\xE9s hacer con las skills globales?",
445
+ choices: [
446
+ { name: "Instalar / actualizar", value: "install" },
447
+ { name: "Eliminar", value: "remove" }
448
+ ]
449
+ }
450
+ ]);
451
+ return action;
452
+ }
453
+ async function askGlobalSkillsToo() {
454
+ const { global } = await inquirer.prompt([
455
+ {
456
+ type: "confirm",
457
+ name: "global",
458
+ message: "\xBFInstalar las skills tambi\xE9n globalmente (OpenCode/Pi/Agent Skills)?",
459
+ default: false
460
+ }
461
+ ]);
462
+ return global;
463
+ }
464
+
465
+ // src/global-skills.ts
466
+ import { mkdir as mkdir2, cp as cp2, rm as rm2, readdir as readdir2 } from "fs/promises";
467
+ import { join as join2 } from "path";
468
+ function resolveGlobalTargets() {
469
+ const home = process.env.HOME;
470
+ if (!home) return [];
471
+ return [
472
+ {
473
+ id: "opencode",
474
+ label: `OpenCode global (${home}/.config/opencode)`,
475
+ root: join2(home, ".config", "opencode"),
476
+ skillsDir: "skills",
477
+ commandsDir: "command"
478
+ },
479
+ {
480
+ id: "pi",
481
+ label: `Pi global (${home}/.pi/agent)`,
482
+ root: join2(home, ".pi", "agent"),
483
+ skillsDir: "skills",
484
+ commandsDir: "prompts"
485
+ },
486
+ {
487
+ id: "agents",
488
+ label: `Agent Skills global (${home}/.agents/skills)`,
489
+ root: join2(home, ".agents"),
490
+ skillsDir: "skills"
491
+ }
492
+ ];
493
+ }
494
+ function filterBundleByNames(only) {
495
+ const set = new Set(only);
496
+ const bundle = AGENT_BUNDLE.filter((e) => set.has(e.dir));
497
+ const known = new Set(AGENT_BUNDLE.map((e) => e.dir));
498
+ return { bundle, unknown: only.filter((n) => !known.has(n)) };
499
+ }
500
+ function mapPlanToTarget(plan, target) {
501
+ const mapped = [];
502
+ for (const item of plan.installs) {
503
+ if (item.to.startsWith("skills/")) {
504
+ mapped.push({
505
+ from: item.from,
506
+ to: join2(target.root, target.skillsDir, item.to.slice("skills/".length)),
507
+ recursive: item.recursive
508
+ });
509
+ continue;
510
+ }
511
+ if (item.to.startsWith("commands/")) {
512
+ if (!target.commandsDir) continue;
513
+ mapped.push({
514
+ from: item.from,
515
+ to: join2(target.root, target.commandsDir, item.to.slice("commands/".length)),
516
+ recursive: item.recursive
517
+ });
518
+ continue;
519
+ }
520
+ }
521
+ return mapped;
522
+ }
523
+ async function installGlobalSkills(repoRoot, targets, bundle = AGENT_BUNDLE) {
524
+ const plan = await planAgentBundle(repoRoot, bundle);
525
+ const results = [];
526
+ for (const target of targets) {
527
+ try {
528
+ let copied = 0;
529
+ for (const item of mapPlanToTarget(plan, target)) {
530
+ if (item.recursive === true) {
531
+ await rm2(item.to, { recursive: true, force: true });
532
+ }
533
+ await mkdir2(
534
+ item.recursive === true ? item.to : join2(item.to, ".."),
535
+ { recursive: true }
536
+ );
537
+ await cp2(item.from, item.to, {
538
+ recursive: item.recursive === true
539
+ });
540
+ copied++;
541
+ }
542
+ results.push({ target, copied });
543
+ } catch (error) {
544
+ results.push({
545
+ target,
546
+ copied: 0,
547
+ error: error.message
548
+ });
549
+ }
550
+ }
551
+ return results;
552
+ }
553
+ async function uninstallGlobalSkills(targets, bundle = AGENT_BUNDLE) {
554
+ const skillNames = bundle.map((e) => e.dir);
555
+ const results = [];
556
+ for (const target of targets) {
557
+ try {
558
+ const removed = [];
559
+ for (const name of skillNames) {
560
+ await rm2(join2(target.root, target.skillsDir, name), {
561
+ recursive: true,
562
+ force: true
563
+ });
564
+ removed.push(`${target.skillsDir}/${name}`);
565
+ }
566
+ if (target.commandsDir) {
567
+ const cmdDir = join2(target.root, target.commandsDir);
568
+ for (const name of skillNames) {
569
+ await rm2(join2(cmdDir, `${name}.md`), { force: true });
570
+ removed.push(`${target.commandsDir}/${name}.md`);
571
+ try {
572
+ const prefix = `${name}-`;
573
+ for (const f of await readdir2(cmdDir)) {
574
+ if (f.startsWith(prefix) && f.endsWith(".md")) {
575
+ await rm2(join2(cmdDir, f), { force: true });
576
+ removed.push(`${target.commandsDir}/${f}`);
577
+ }
578
+ }
579
+ } catch {
580
+ }
581
+ }
582
+ }
583
+ results.push({ target, removed });
584
+ } catch (error) {
585
+ results.push({ target, removed: [], error: error.message });
586
+ }
587
+ }
588
+ return results;
589
+ }
407
590
 
408
591
  // src/utils.ts
409
592
  import { existsSync } from "fs";
@@ -507,9 +690,9 @@ function adaptScriptToNode(script) {
507
690
  return s;
508
691
  }
509
692
  async function adaptToNodeRuntime(projectPath) {
510
- const { readFile: readFile3, writeFile: writeFile2, readdir: readdir2 } = await import("fs/promises");
511
- const { join: join3 } = await import("path");
512
- const pkgPath = join3(projectPath, "package.json");
693
+ const { readFile: readFile3, writeFile: writeFile2, readdir: readdir3 } = await import("fs/promises");
694
+ const { join: join4 } = await import("path");
695
+ const pkgPath = join4(projectPath, "package.json");
513
696
  const pkg = JSON.parse(await readFile3(pkgPath, "utf-8"));
514
697
  for (const key of Object.keys(pkg.scripts ?? {})) {
515
698
  pkg.scripts[key] = adaptScriptToNode(pkg.scripts[key]);
@@ -520,9 +703,9 @@ async function adaptToNodeRuntime(projectPath) {
520
703
  delete pkg.devDependencies["bun-types"];
521
704
  await writeFile2(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
522
705
  async function walk(dir) {
523
- const entries = await readdir2(dir, { withFileTypes: true });
706
+ const entries = await readdir3(dir, { withFileTypes: true });
524
707
  for (const entry of entries) {
525
- const full = join3(dir, entry.name);
708
+ const full = join4(dir, entry.name);
526
709
  if (entry.isDirectory()) {
527
710
  if (entry.name !== "node_modules") await walk(full);
528
711
  } else if (entry.name.endsWith(".test.ts")) {
@@ -533,12 +716,12 @@ async function adaptToNodeRuntime(projectPath) {
533
716
  }
534
717
  }
535
718
  }
536
- const srcDir = join3(projectPath, "src");
719
+ const srcDir = join4(projectPath, "src");
537
720
  try {
538
721
  await walk(srcDir);
539
722
  } catch {
540
723
  }
541
- const tsconfigPath = join3(projectPath, "tsconfig.json");
724
+ const tsconfigPath = join4(projectPath, "tsconfig.json");
542
725
  try {
543
726
  const tsconfig = JSON.parse(await readFile3(tsconfigPath, "utf-8"));
544
727
  if (tsconfig.compilerOptions?.types?.includes("bun-types")) {
@@ -550,8 +733,8 @@ async function adaptToNodeRuntime(projectPath) {
550
733
  }
551
734
  async function writeRootGitignore(projectPath) {
552
735
  const { writeFile: writeFile2, access } = await import("fs/promises");
553
- const { join: join3 } = await import("path");
554
- const gitignorePath = join3(projectPath, ".gitignore");
736
+ const { join: join4 } = await import("path");
737
+ const gitignorePath = join4(projectPath, ".gitignore");
555
738
  try {
556
739
  await access(gitignorePath);
557
740
  } catch {
@@ -579,7 +762,7 @@ async function gitInit(projectPath) {
579
762
  }
580
763
  async function writeRootFiles(info) {
581
764
  const { writeFile: writeFile2 } = await import("fs/promises");
582
- const { join: join3 } = await import("path");
765
+ const { join: join4 } = await import("path");
583
766
  const projectPath = resolve(process.cwd(), info.projectName);
584
767
  await writeRootGitignore(projectPath);
585
768
  const installB = info.backend.postInit?.install ? resolveCommand(info.backend.postInit.install, info.pm) : `${info.pm} install`;
@@ -595,7 +778,7 @@ async function writeRootFiles(info) {
595
778
  "",
596
779
  `- \`backend/\` \u2014 ${info.backend.description}`,
597
780
  `- \`frontend/\` \u2014 ${info.frontend.description}`,
598
- "- `.opencode/`, `.pi/` y `.agents/` \u2014 skills de agentes: `/create-specs` para las specs + biblioteca de dise\xF1o (dark-luxury, minimal-dashboard, minimal-light, neo-brutalist, glassmorphism). Mismo bundle en `.opencode/` (opencode), `.pi/` (Pi) y `.agents/` (Freebuff / est\xE1ndar Agent Skills)",
781
+ "- `.opencode/`, `.pi/` y `.agents/` \u2014 skills de agentes: `/create-prompt` para detallar tu idea, `/create-specs` para generar las specs, `/create-specs-from-code` para documentar un proyecto que ya existe, + biblioteca de dise\xF1o (dark-luxury, minimal-dashboard, minimal-light, neo-brutalist, glassmorphism). Mismo bundle en las 3 ubicaciones",
599
782
  "- `specs/` \u2014 especificaciones del proyecto",
600
783
  "",
601
784
  "## Backend",
@@ -615,7 +798,7 @@ async function writeRootFiles(info) {
615
798
  "```",
616
799
  ""
617
800
  ].join("\n");
618
- await writeFile2(join3(projectPath, "README.md"), readme);
801
+ await writeFile2(join4(projectPath, "README.md"), readme);
619
802
  }
620
803
 
621
804
  // src/index.ts
@@ -634,15 +817,15 @@ var AGENT_DIRS = [".opencode", ".pi", ".agents"];
634
817
  async function installAgentSkills(repoRoot, projectPath) {
635
818
  const plan = await planAgentBundle(repoRoot);
636
819
  for (const agentDir of AGENT_DIRS) {
637
- const root = join2(projectPath, agentDir);
820
+ const root = join3(projectPath, agentDir);
638
821
  for (const dir of plan.skillDirs) {
639
- await mkdir2(join2(root, dir), { recursive: true });
822
+ await mkdir3(join3(root, dir), { recursive: true });
640
823
  }
641
824
  for (const dir of plan.commandDirs) {
642
- await mkdir2(join2(root, dir), { recursive: true });
825
+ await mkdir3(join3(root, dir), { recursive: true });
643
826
  }
644
827
  for (const item of plan.installs) {
645
- await cp2(item.from, join2(root, item.to), {
828
+ await cp3(item.from, join3(root, item.to), {
646
829
  recursive: item.recursive === true
647
830
  });
648
831
  }
@@ -656,10 +839,29 @@ async function installAgentSkills(repoRoot, projectPath) {
656
839
  );
657
840
  }
658
841
  }
842
+ async function installGlobalSkillsWithSpinner(targets, bundle) {
843
+ const spinner = ora("Descargando bundle de skills...").start();
844
+ const { tempDir, repoRoot } = await downloadAndExtractRepo();
845
+ try {
846
+ spinner.text = "Instalando skills...";
847
+ const results = await installGlobalSkills(repoRoot, targets, bundle);
848
+ spinner.succeed("Skills globales instaladas");
849
+ for (const r of results) {
850
+ if (r.error) {
851
+ console.log(chalk.red(` \u2716 ${r.target.label}: ${r.error}`));
852
+ } else {
853
+ console.log(chalk.dim(` \u2714 ${r.target.label} \u2014 ${r.copied} archivos`));
854
+ }
855
+ }
856
+ console.log();
857
+ } finally {
858
+ await cleanup(tempDir);
859
+ }
860
+ }
659
861
  async function readTemplatePostInit(dir) {
660
862
  try {
661
863
  const tj = JSON.parse(
662
- await readFile2(join2(dir, "template.json"), "utf-8")
864
+ await readFile2(join3(dir, "template.json"), "utf-8")
663
865
  );
664
866
  return tj.postInit;
665
867
  } catch {
@@ -669,14 +871,14 @@ async function readTemplatePostInit(dir) {
669
871
  async function createFullStackProject(projectPath, backendTpl, frontendTpl, pm) {
670
872
  const { tempDir, repoRoot } = await downloadAndExtractRepo();
671
873
  const projectName = basename(projectPath);
672
- const backendPath = join2(projectPath, "backend");
673
- const frontendPath = join2(projectPath, "frontend");
874
+ const backendPath = join3(projectPath, "backend");
875
+ const frontendPath = join3(projectPath, "frontend");
674
876
  await copyTemplate(
675
- join2(repoRoot, "templates", backendTpl.folder),
877
+ join3(repoRoot, "templates", backendTpl.folder),
676
878
  backendPath
677
879
  );
678
880
  await copyTemplate(
679
- join2(repoRoot, "templates", frontendTpl.folder),
881
+ join3(repoRoot, "templates", frontendTpl.folder),
680
882
  frontendPath
681
883
  );
682
884
  await substituteTemplate(backendPath, projectName, backendTpl.folder);
@@ -735,6 +937,102 @@ program.command("list").description("Mostrar templates disponibles").action(() =
735
937
  console.log();
736
938
  }
737
939
  });
940
+ async function runSkillsFlow(opts = {}) {
941
+ const all = resolveGlobalTargets();
942
+ if (all.length === 0) {
943
+ console.error(chalk.red("\n\u2716 No se pudo resolver el HOME del usuario\n"));
944
+ process.exit(1);
945
+ }
946
+ console.log(chalk.bold("\nSkills disponibles:\n"));
947
+ console.log(chalk.dim(" create-specs \u2014 specs para proyecto nuevo (/create-specs)"));
948
+ console.log(chalk.dim(" create-specs-from-code \u2014 specs desde c\xF3digo existente (/create-specs-from-code)"));
949
+ console.log(chalk.dim(" design/ \u2014 dark-luxury, glassmorphism, minimal-dashboard, minimal-light, neo-brutalist (/design-<estilo>)\n"));
950
+ let bundle;
951
+ if (opts.only) {
952
+ const names = opts.only.split(",").map((s) => s.trim()).filter(Boolean);
953
+ const { bundle: filtered, unknown } = filterBundleByNames(names);
954
+ if (unknown.length > 0) {
955
+ console.log(chalk.yellow(` \u26A0 Skills desconocidas (ignoradas): ${unknown.join(", ")}
956
+ `));
957
+ }
958
+ if (filtered.length === 0) {
959
+ console.log(chalk.dim("\n Ninguna skill v\xE1lida en --only \u2014 no se hizo nada.\n"));
960
+ return false;
961
+ }
962
+ bundle = filtered;
963
+ }
964
+ const remove = opts.remove === true;
965
+ const interactive = !opts.global;
966
+ let action = remove ? "remove" : "install";
967
+ if (interactive && !remove) {
968
+ action = await selectSkillsAction();
969
+ }
970
+ let targets;
971
+ if (opts.global) {
972
+ const ids = opts.global.split(",").map((s) => s.trim().toLowerCase());
973
+ if (ids.includes("all")) {
974
+ targets = all;
975
+ } else {
976
+ const valid = new Map(all.map((t) => [t.id, t]));
977
+ targets = [];
978
+ for (const id of ids) {
979
+ const t = valid.get(id);
980
+ if (!t) {
981
+ console.error(
982
+ chalk.red(`
983
+ \u2716 Destino "${id}" inv\xE1lido. Us\xE1: ${all.map((x) => x.id).join(", ")} o all
984
+ `)
985
+ );
986
+ process.exit(1);
987
+ }
988
+ targets.push(t);
989
+ }
990
+ }
991
+ } else {
992
+ const pick = action === "remove" ? selectGlobalTargetsToRemove : selectGlobalTargets;
993
+ const selected = await pick(all.map((t) => ({ name: t.label, value: t.id })));
994
+ if (selected.length === 0) {
995
+ console.log(
996
+ chalk.dim(`
997
+ Nada seleccionado \u2014 no se ${action === "remove" ? "elimin\xF3" : "instal\xF3"} nada.
998
+ `)
999
+ );
1000
+ return false;
1001
+ }
1002
+ targets = all.filter((t) => selected.includes(t.id));
1003
+ }
1004
+ if (action === "remove") {
1005
+ const results = await uninstallGlobalSkills(targets, bundle);
1006
+ for (const r of results) {
1007
+ console.log(chalk.dim(` \u2714 ${r.target.label} \u2014 ${r.removed.length} eliminados`));
1008
+ }
1009
+ console.log();
1010
+ return true;
1011
+ }
1012
+ await installGlobalSkillsWithSpinner(targets, bundle);
1013
+ return true;
1014
+ }
1015
+ program.command("skills").description(
1016
+ "Instalar o eliminar las skills del repo en ubicaciones globales (OpenCode, Pi, Agent Skills)"
1017
+ ).option(
1018
+ "-g, --global <targets>",
1019
+ "Destinos separados por coma: opencode, pi, agents (o 'all'). Saltea la pregunta"
1020
+ ).option(
1021
+ "-o, --only <skills>",
1022
+ "Solo estas skills, separadas por coma: create-specs, create-specs-from-code, design"
1023
+ ).option(
1024
+ "-r, --remove",
1025
+ "Eliminar en vez de instalar (combinar con --global y opcionalmente --only)"
1026
+ ).action(async (opts) => {
1027
+ try {
1028
+ await runSkillsFlow(opts);
1029
+ } catch (error) {
1030
+ console.error(chalk.red(`
1031
+ \u2716 Error: ${error.message}
1032
+ `));
1033
+ process.exit(1);
1034
+ }
1035
+ });
738
1036
  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
1037
  async (templateArg, projectNameArg) => {
740
1038
  try {
@@ -756,6 +1054,10 @@ program.argument("[template]", "Template a usar (o 'fullstack' para backend + fr
756
1054
  const rawArg = templateArg?.toLowerCase();
757
1055
  if (rawArg === void 0) {
758
1056
  const scope = await selectScope();
1057
+ if (scope === "skills") {
1058
+ await runSkillsFlow();
1059
+ return;
1060
+ }
759
1061
  if (scope === "fullstack") {
760
1062
  isFullStack = true;
761
1063
  } else {
@@ -853,7 +1155,7 @@ program.argument("[template]", "Template a usar (o 'fullstack' para backend + fr
853
1155
  spinner.succeed("Template descargado");
854
1156
  } else {
855
1157
  const { tempDir, templatePath, repoRoot } = await downloadAndExtract(template.folder);
856
- const codePath = useLayerLayout ? join2(projectPath, layer) : projectPath;
1158
+ const codePath = useLayerLayout ? join3(projectPath, layer) : projectPath;
857
1159
  spinner.text = "Creando proyecto...";
858
1160
  await copyTemplate(templatePath, codePath);
859
1161
  await substituteTemplate(codePath, projectName, template.folder);
@@ -896,6 +1198,28 @@ program.argument("[template]", "Template a usar (o 'fullstack' para backend + fr
896
1198
  );
897
1199
  }
898
1200
  }
1201
+ if (git === void 0) {
1202
+ const wantsGlobalSkills = await askGlobalSkillsToo();
1203
+ if (wantsGlobalSkills) {
1204
+ const all = resolveGlobalTargets();
1205
+ if (all.length === 0) {
1206
+ console.log(
1207
+ chalk.yellow(
1208
+ " \u26A0 No se pudo resolver el HOME \u2014 skills globales omitidas\n"
1209
+ )
1210
+ );
1211
+ } else {
1212
+ const selected = await selectGlobalTargets(
1213
+ all.map((t) => ({ name: t.label, value: t.id }))
1214
+ );
1215
+ if (selected.length > 0) {
1216
+ await installGlobalSkillsWithSpinner(
1217
+ all.filter((t) => selected.includes(t.id))
1218
+ );
1219
+ }
1220
+ }
1221
+ }
1222
+ }
899
1223
  } catch (error) {
900
1224
  console.error(
901
1225
  chalk.red(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fwinit",
3
- "version": "1.4.5",
3
+ "version": "1.4.7",
4
4
  "description": "CLI para crear proyectos desde templates",
5
5
  "type": "module",
6
6
  "bin": {