fwinit 1.5.2 → 1.5.4

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 +8 -6
  2. package/dist/index.js +58 -18
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -35,11 +35,12 @@ 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
38
+ # Instalar skills globalmente (OpenCode, Pi, Gentle Shell, Agent Skills) — interactivo
39
39
  fwinit skills
40
40
 
41
41
  # Instalar skills globalmente sin preguntas (modo scripting/CI)
42
42
  fwinit skills --global opencode,pi # o: --global all
43
+ fwinit skills --global gentle-shell
43
44
 
44
45
  # Instalar solo algunas skills
45
46
  fwinit skills --global all --only create-specs,design
@@ -80,9 +81,10 @@ Pregunta qué backend querés (ASP.NET, Express, Fastify, Node.js Vanilla) y qu
80
81
  mi-proyecto/
81
82
  ├── backend/ # API (template elegido — trae su DB: Prisma o EF)
82
83
  ├── frontend/ # App (template frontend elegido)
83
- ├── .gitignore # .atl/ · odd (.opencode/ y .pi/ SÍ se versionan)
84
- ├── .opencode/ # Skills de opencode (create-specs, create-specs-from-code y biblioteca de diseño)
85
- ├── .pi/ # Mismo bundle de skills para Pi (badlogic)
84
+ ├── .gitignore # .atl/ · odd (.opencode/, .pi/ y .agents/ SÍ se versionan)
85
+ ├── .opencode/ # Skills de OpenCode (create-specs, create-specs-from-code, create-prompt + biblioteca de diseño)
86
+ ├── .pi/ # Mismo bundle para Pi y Gentle Shell (skills + prompt templates)
87
+ ├── .agents/ # Mismo bundle para Freebuff y agentes Agent Skills
86
88
  ├── README.md # Cómo correr backend y frontend
87
89
  └── specs/ # La creás con /create-specs
88
90
  ```
@@ -138,12 +140,12 @@ El runtime `bun` de un template se porta automáticamente a `node` (npm/pnpm) si
138
140
  2. Extrae y copia el template (o los dos, `backend/` + `frontend/`) al proyecto
139
141
  3. Sustituye el nombre del proyecto (package.json, app.json de Expo, proyectos ASP.NET con "Example")
140
142
  4. Si corresponde, porta el template de runtime bun a node (npm/pnpm)
141
- 5. Instala `.opencode/` y `.pi/` con las skills `create-specs` y `create-specs-from-code`, sus comandos (`/create-specs` rutea entre ambos según el estado del repo) y la biblioteca de diseño (mismo bundle para opencode y Pi: elegí el agente que quieras)
143
+ 5. Instala `.opencode/`, `.pi/` y `.agents/` con las skills `create-specs` y `create-specs-from-code`, sus comandos (`/create-specs` rutea entre ambos según el estado del repo) y la biblioteca de diseño (mismo bundle para OpenCode, Pi/Gentle Shell y Agent Skills: elegí el agente que quieras)
142
144
  6. Limpia los lock files (cada proyecto instala los suyos)
143
145
  7. Si lo elegís (o pasás `--git`), inicializa un repositorio git en la raíz
144
146
  8. Te muestra los próximos pasos (install y dev de cada carpeta)
145
147
 
146
- En modo fullstack (y single con layout de capa) crea además `.gitignore` y `README.md` en la raíz: el `.gitignore` excluye solo el estado local de AI (`.atl/` y `odd`). `.opencode/` y `.pi/` NO se ignoran — los bundles de skills se versionan con el proyecto.
148
+ En modo fullstack (y single con layout de capa) crea además `.gitignore` y `README.md` en la raíz: el `.gitignore` excluye solo el estado local de AI (`.atl/` y `odd`). `.opencode/`, `.pi/` y `.agents/` NO se ignoran — los bundles de skills se versionan con el proyecto.
147
149
 
148
150
  ## Desarrollo
149
151
 
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ 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 mkdir3, cp as cp3, readFile as readFile2 } from "fs/promises";
8
+ import { mkdir as mkdir3, cp as cp3, readFile as readFile3 } from "fs/promises";
9
9
  import { join as join3, basename } from "path";
10
10
 
11
11
  // src/templates.ts
@@ -455,7 +455,7 @@ async function askGlobalSkillsToo() {
455
455
  {
456
456
  type: "confirm",
457
457
  name: "global",
458
- message: "\xBFInstalar las skills tambi\xE9n globalmente (OpenCode/Pi/Agent Skills)?",
458
+ message: "\xBFInstalar las skills tambi\xE9n globalmente (OpenCode/Pi/Gentle Shell/Agent Skills)?",
459
459
  default: false
460
460
  }
461
461
  ]);
@@ -463,11 +463,12 @@ async function askGlobalSkillsToo() {
463
463
  }
464
464
 
465
465
  // src/global-skills.ts
466
- import { mkdir as mkdir2, cp as cp2, rm as rm2, readdir as readdir2 } from "fs/promises";
466
+ import { mkdir as mkdir2, cp as cp2, rm as rm2, readdir as readdir2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
467
467
  import { join as join2 } from "path";
468
468
  function resolveGlobalTargets() {
469
469
  const home = process.env.HOME;
470
470
  if (!home) return [];
471
+ const gentleShellRoot = process.env.GENTLE_SHELL_HOME || join2(home, ".gentle-shell", "agent");
471
472
  return [
472
473
  {
473
474
  id: "opencode",
@@ -483,6 +484,13 @@ function resolveGlobalTargets() {
483
484
  skillsDir: "skills",
484
485
  commandsDir: "prompts"
485
486
  },
487
+ {
488
+ id: "gentle-shell",
489
+ label: `Gentle Shell global (${gentleShellRoot})`,
490
+ root: gentleShellRoot,
491
+ skillsDir: "skills",
492
+ commandsDir: "prompts"
493
+ },
486
494
  {
487
495
  id: "agents",
488
496
  label: `Agent Skills global (${home}/.agents/skills)`,
@@ -497,6 +505,37 @@ function filterBundleByNames(only) {
497
505
  const known = new Set(AGENT_BUNDLE.map((e) => e.dir));
498
506
  return { bundle, unknown: only.filter((n) => !known.has(n)) };
499
507
  }
508
+ async function ensureGlobalOpencodeConfig() {
509
+ const home = process.env.HOME;
510
+ if (!home) return;
511
+ const configPath = join2(home, ".config", "opencode", "opencode.json");
512
+ try {
513
+ let data;
514
+ try {
515
+ const content = await readFile2(configPath, "utf-8");
516
+ data = JSON.parse(content);
517
+ } catch {
518
+ data = { agent: {} };
519
+ }
520
+ const agent = data.agent ?? {};
521
+ const dangerousGentleman = agent["dangerous-gentleman"] ?? {};
522
+ let changed = false;
523
+ if (dangerousGentleman.parallel_tool_calls !== true) {
524
+ dangerousGentleman.parallel_tool_calls = true;
525
+ changed = true;
526
+ }
527
+ if (dangerousGentleman.max_parallel_calls !== 10) {
528
+ dangerousGentleman.max_parallel_calls = 10;
529
+ changed = true;
530
+ }
531
+ if (changed) {
532
+ agent["dangerous-gentleman"] = dangerousGentleman;
533
+ data.agent = agent;
534
+ await writeFile2(configPath, JSON.stringify(data, null, 2), "utf-8");
535
+ }
536
+ } catch {
537
+ }
538
+ }
500
539
  function mapPlanToTarget(plan, target) {
501
540
  const mapped = [];
502
541
  for (const item of plan.installs) {
@@ -690,10 +729,10 @@ function adaptScriptToNode(script) {
690
729
  return s;
691
730
  }
692
731
  async function adaptToNodeRuntime(projectPath) {
693
- const { readFile: readFile3, writeFile: writeFile2, readdir: readdir3 } = await import("fs/promises");
732
+ const { readFile: readFile4, writeFile: writeFile3, readdir: readdir3 } = await import("fs/promises");
694
733
  const { join: join4 } = await import("path");
695
734
  const pkgPath = join4(projectPath, "package.json");
696
- const pkg = JSON.parse(await readFile3(pkgPath, "utf-8"));
735
+ const pkg = JSON.parse(await readFile4(pkgPath, "utf-8"));
697
736
  for (const key of Object.keys(pkg.scripts ?? {})) {
698
737
  pkg.scripts[key] = adaptScriptToNode(pkg.scripts[key]);
699
738
  }
@@ -701,7 +740,7 @@ async function adaptToNodeRuntime(projectPath) {
701
740
  pkg.devDependencies.tsx = "^4.0.0";
702
741
  pkg.devDependencies.vitest = "^3.0.0";
703
742
  delete pkg.devDependencies["bun-types"];
704
- await writeFile2(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
743
+ await writeFile3(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
705
744
  async function walk(dir) {
706
745
  const entries = await readdir3(dir, { withFileTypes: true });
707
746
  for (const entry of entries) {
@@ -709,9 +748,9 @@ async function adaptToNodeRuntime(projectPath) {
709
748
  if (entry.isDirectory()) {
710
749
  if (entry.name !== "node_modules") await walk(full);
711
750
  } else if (entry.name.endsWith(".test.ts")) {
712
- const content = await readFile3(full, "utf-8");
751
+ const content = await readFile4(full, "utf-8");
713
752
  if (content.includes("bun:test")) {
714
- await writeFile2(full, content.replaceAll('from "bun:test"', 'from "vitest"'));
753
+ await writeFile3(full, content.replaceAll('from "bun:test"', 'from "vitest"'));
715
754
  }
716
755
  }
717
756
  }
@@ -723,22 +762,22 @@ async function adaptToNodeRuntime(projectPath) {
723
762
  }
724
763
  const tsconfigPath = join4(projectPath, "tsconfig.json");
725
764
  try {
726
- const tsconfig = JSON.parse(await readFile3(tsconfigPath, "utf-8"));
765
+ const tsconfig = JSON.parse(await readFile4(tsconfigPath, "utf-8"));
727
766
  if (tsconfig.compilerOptions?.types?.includes("bun-types")) {
728
767
  delete tsconfig.compilerOptions.types;
729
- await writeFile2(tsconfigPath, JSON.stringify(tsconfig, null, 2) + "\n");
768
+ await writeFile3(tsconfigPath, JSON.stringify(tsconfig, null, 2) + "\n");
730
769
  }
731
770
  } catch {
732
771
  }
733
772
  }
734
773
  async function writeRootGitignore(projectPath) {
735
- const { writeFile: writeFile2, access } = await import("fs/promises");
774
+ const { writeFile: writeFile3, access } = await import("fs/promises");
736
775
  const { join: join4 } = await import("path");
737
776
  const gitignorePath = join4(projectPath, ".gitignore");
738
777
  try {
739
778
  await access(gitignorePath);
740
779
  } catch {
741
- await writeFile2(
780
+ await writeFile3(
742
781
  gitignorePath,
743
782
  "# Local AI runtime state\n.atl/\nodd\n"
744
783
  );
@@ -761,7 +800,7 @@ async function gitInit(projectPath) {
761
800
  }
762
801
  }
763
802
  async function writeRootFiles(info) {
764
- const { writeFile: writeFile2 } = await import("fs/promises");
803
+ const { writeFile: writeFile3 } = await import("fs/promises");
765
804
  const { join: join4 } = await import("path");
766
805
  const projectPath = resolve(process.cwd(), info.projectName);
767
806
  await writeRootGitignore(projectPath);
@@ -778,7 +817,7 @@ async function writeRootFiles(info) {
778
817
  "",
779
818
  `- \`backend/\` \u2014 ${info.backend.description}`,
780
819
  `- \`frontend/\` \u2014 ${info.frontend.description}`,
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",
820
+ "- `.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: OpenCode lee `.opencode/`; Pi y Gentle Shell leen `.pi/` (skills + prompt templates); los agentes Agent Skills leen `.agents/`",
782
821
  "- `specs/` \u2014 especificaciones del proyecto",
783
822
  "",
784
823
  "## Backend",
@@ -798,7 +837,7 @@ async function writeRootFiles(info) {
798
837
  "```",
799
838
  ""
800
839
  ].join("\n");
801
- await writeFile2(join4(projectPath, "README.md"), readme);
840
+ await writeFile3(join4(projectPath, "README.md"), readme);
802
841
  }
803
842
 
804
843
  // src/index.ts
@@ -853,6 +892,7 @@ async function installGlobalSkillsWithSpinner(targets, bundle) {
853
892
  console.log(chalk.dim(` \u2714 ${r.target.label} \u2014 ${r.copied} archivos`));
854
893
  }
855
894
  }
895
+ await ensureGlobalOpencodeConfig();
856
896
  console.log();
857
897
  } finally {
858
898
  await cleanup(tempDir);
@@ -861,7 +901,7 @@ async function installGlobalSkillsWithSpinner(targets, bundle) {
861
901
  async function readTemplatePostInit(dir) {
862
902
  try {
863
903
  const tj = JSON.parse(
864
- await readFile2(join3(dir, "template.json"), "utf-8")
904
+ await readFile3(join3(dir, "template.json"), "utf-8")
865
905
  );
866
906
  return tj.postInit;
867
907
  } catch {
@@ -1013,10 +1053,10 @@ async function runSkillsFlow(opts = {}) {
1013
1053
  return true;
1014
1054
  }
1015
1055
  program.command("skills").description(
1016
- "Instalar o eliminar las skills del repo en ubicaciones globales (OpenCode, Pi, Agent Skills)"
1056
+ "Instalar o eliminar las skills del repo en ubicaciones globales (OpenCode, Pi, Gentle Shell, Agent Skills)"
1017
1057
  ).option(
1018
1058
  "-g, --global <targets>",
1019
- "Destinos separados por coma: opencode, pi, agents (o 'all'). Saltea la pregunta"
1059
+ "Destinos separados por coma: opencode, pi, gentle-shell, agents (o 'all'). Saltea la pregunta"
1020
1060
  ).option(
1021
1061
  "-o, --only <skills>",
1022
1062
  "Solo estas skills, separadas por coma: create-specs, create-specs-from-code, design"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fwinit",
3
- "version": "1.5.2",
3
+ "version": "1.5.4",
4
4
  "description": "CLI para crear proyectos desde templates",
5
5
  "type": "module",
6
6
  "bin": {