fwinit 1.4.4 → 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.
Files changed (3) hide show
  1. package/README.md +15 -0
  2. package/dist/index.js +352 -37
  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 = [
@@ -266,33 +266,33 @@ async function exists(path) {
266
266
  }
267
267
  async function listMarkdown(dir) {
268
268
  try {
269
- const { readdir: readdir2 } = await import("fs/promises");
270
- return (await readdir2(dir)).filter((f) => f.endsWith(".md"));
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: join3 } = await import("path");
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 = join3(repoRoot, "skills", entry.dir);
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 = join3(src, "commands");
288
+ const cmds = join4(src, "commands");
289
289
  for (const file of await listMarkdown(cmds)) {
290
- installs.push({ from: join3(cmds, file), to: `commands/${file}` });
290
+ installs.push({ from: join4(cmds, file), to: `commands/${file}` });
291
291
  }
292
292
  continue;
293
293
  }
294
- const skill = join3(src, "SKILL.md");
295
- const command = join3(src, "command.md");
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 = join3(src, extra);
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: readdir2 } = await import("fs/promises");
511
- const { join: join3 } = await import("path");
512
- const pkgPath = join3(projectPath, "package.json");
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 readdir2(dir, { withFileTypes: true });
697
+ const entries = await readdir3(dir, { withFileTypes: true });
524
698
  for (const entry of entries) {
525
- const full = join3(dir, entry.name);
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 = join3(projectPath, "src");
710
+ const srcDir = join4(projectPath, "src");
537
711
  try {
538
712
  await walk(srcDir);
539
713
  } catch {
540
714
  }
541
- const tsconfigPath = join3(projectPath, "tsconfig.json");
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: join3 } = await import("path");
554
- const gitignorePath = join3(projectPath, ".gitignore");
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: join3 } = await import("path");
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`;
@@ -595,7 +769,7 @@ async function writeRootFiles(info) {
595
769
  "",
596
770
  `- \`backend/\` \u2014 ${info.backend.description}`,
597
771
  `- \`frontend/\` \u2014 ${info.frontend.description}`,
598
- "- `.opencode/` y `.pi/` \u2014 skills de agentes: `/create-specs` para las specs + biblioteca de dise\xF1o (dark-luxury, minimal-light, neo-brutalist, glassmorphism). Mismo bundle en `.opencode/` (opencode) y `.pi/` (Pi)",
772
+ "- `.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)",
599
773
  "- `specs/` \u2014 especificaciones del proyecto",
600
774
  "",
601
775
  "## Backend",
@@ -615,7 +789,7 @@ async function writeRootFiles(info) {
615
789
  "```",
616
790
  ""
617
791
  ].join("\n");
618
- await writeFile2(join3(projectPath, "README.md"), readme);
792
+ await writeFile2(join4(projectPath, "README.md"), readme);
619
793
  }
620
794
 
621
795
  // src/index.ts
@@ -630,19 +804,19 @@ function getCliVersion() {
630
804
  return "0.0.0";
631
805
  }
632
806
  }
633
- var AGENT_DIRS = [".opencode", ".pi"];
807
+ 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 = join2(projectPath, agentDir);
811
+ const root = join3(projectPath, agentDir);
638
812
  for (const dir of plan.skillDirs) {
639
- await mkdir2(join2(root, dir), { recursive: true });
813
+ await mkdir3(join3(root, dir), { recursive: true });
640
814
  }
641
815
  for (const dir of plan.commandDirs) {
642
- await mkdir2(join2(root, dir), { recursive: true });
816
+ await mkdir3(join3(root, dir), { recursive: true });
643
817
  }
644
818
  for (const item of plan.installs) {
645
- await cp2(item.from, join2(root, item.to), {
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(join2(dir, "template.json"), "utf-8")
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 = join2(projectPath, "backend");
673
- const frontendPath = join2(projectPath, "frontend");
865
+ const backendPath = join3(projectPath, "backend");
866
+ const frontendPath = join3(projectPath, "frontend");
674
867
  await copyTemplate(
675
- join2(repoRoot, "templates", backendTpl.folder),
868
+ join3(repoRoot, "templates", backendTpl.folder),
676
869
  backendPath
677
870
  );
678
871
  await copyTemplate(
679
- join2(repoRoot, "templates", frontendTpl.folder),
872
+ join3(repoRoot, "templates", frontendTpl.folder),
680
873
  frontendPath
681
874
  );
682
875
  await substituteTemplate(backendPath, projectName, backendTpl.folder);
@@ -712,7 +905,7 @@ async function createFullStackProject(projectPath, backendTpl, frontendTpl, pm)
712
905
  );
713
906
  console.log(
714
907
  chalk.dim(
715
- " Estructura: backend/ (API) \xB7 frontend/ (app) \xB7 .opencode/, .pi/ y specs/ (al mismo nivel)\n"
908
+ " Estructura: backend/ (API) \xB7 frontend/ (app) \xB7 .opencode/, .pi/, .agents/ y specs/ (al mismo nivel)\n"
716
909
  )
717
910
  );
718
911
  }
@@ -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 ? join2(projectPath, layer) : projectPath;
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);
@@ -876,7 +1169,7 @@ program.argument("[template]", "Template a usar (o 'fullstack' para backend + fr
876
1169
  if (useLayerLayout) {
877
1170
  console.log(
878
1171
  chalk.dim(
879
- ` Estructura: ${layer}/ (c\xF3digo) \xB7 .opencode/, .pi/ y specs/ (al mismo nivel)
1172
+ ` Estructura: ${layer}/ (c\xF3digo) \xB7 .opencode/, .pi/, .agents/ y specs/ (al mismo nivel)
880
1173
  `
881
1174
  )
882
1175
  );
@@ -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(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fwinit",
3
- "version": "1.4.4",
3
+ "version": "1.4.6",
4
4
  "description": "CLI para crear proyectos desde templates",
5
5
  "type": "module",
6
6
  "bin": {