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.
- package/README.md +8 -6
- package/dist/index.js +58 -18
- 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 .
|
|
84
|
-
├── .opencode/ # Skills de
|
|
85
|
-
├── .pi/ # Mismo bundle
|
|
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 `.
|
|
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 `.
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
751
|
+
const content = await readFile4(full, "utf-8");
|
|
713
752
|
if (content.includes("bun:test")) {
|
|
714
|
-
await
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
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"
|