fwinit 1.4.2 → 1.4.3

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 +2 -2
  2. package/dist/index.js +113 -53
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -66,7 +66,7 @@ mi-proyecto/
66
66
  ├── backend/ # API (template elegido — trae su DB: Prisma o EF)
67
67
  ├── frontend/ # App (template frontend elegido)
68
68
  ├── .gitignore # .atl/ · odd (.opencode/ y .pi/ SÍ se versionan)
69
- ├── .opencode/ # Skills de opencode (create-specs incluida)
69
+ ├── .opencode/ # Skills de opencode (create-specs, create-specs-from-code y biblioteca de diseño)
70
70
  ├── .pi/ # Mismo bundle de skills para Pi (badlogic)
71
71
  ├── README.md # Cómo correr backend y frontend
72
72
  └── specs/ # La creás con /create-specs
@@ -123,7 +123,7 @@ El runtime `bun` de un template se porta automáticamente a `node` (npm/pnpm) si
123
123
  2. Extrae y copia el template (o los dos, `backend/` + `frontend/`) al proyecto
124
124
  3. Sustituye el nombre del proyecto (package.json, app.json de Expo, proyectos ASP.NET con "Example")
125
125
  4. Si corresponde, porta el template de runtime bun a node (npm/pnpm)
126
- 5. Instala `.opencode/` y `.pi/` con la skill `create-specs` y el comando `/create-specs` (mismo bundle para opencode y Pi: elegí el agente que quieras)
126
+ 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)
127
127
  6. Limpia los lock files (cada proyecto instala los suyos)
128
128
  7. Si lo elegís (o pasás `--git`), inicializa un repositorio git en la raíz
129
129
  8. Te muestra los próximos pasos (install y dev de cada carpeta)
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 mkdir2, cp as cp2, access, readFile as readFile2, readdir as readdir2 } from "fs/promises";
8
+ import { mkdir as mkdir2, cp as cp2, readFile as readFile2 } from "fs/promises";
9
9
  import { join as join2, basename } from "path";
10
10
 
11
11
  // src/templates.ts
@@ -228,6 +228,96 @@ function toPascalCase(str) {
228
228
  return str.split(/[-_\s]+/).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
229
229
  }
230
230
 
231
+ // src/agent-bundle.ts
232
+ var AGENT_BUNDLE = [
233
+ {
234
+ kind: "skill",
235
+ dir: "create-specs",
236
+ required: true,
237
+ // Los ejemplos de prompts viajan a <agent>/skills/create-specs/examples/
238
+ // para que la skill pueda mostrarlos dentro del proyecto generado.
239
+ extras: ["examples"]
240
+ },
241
+ {
242
+ // Skill espejo para proyectos que YA existen: misma familia, el comando
243
+ // /create-specs rutea entre las dos según el estado del repo. Comparte el
244
+ // Spec Tree Contract con create-specs, así que no duplica ejemplos: los
245
+ // referencia desde ahí.
246
+ kind: "skill",
247
+ dir: "create-specs-from-code",
248
+ required: true
249
+ },
250
+ {
251
+ // Biblioteca de estilos: skills/design/ con una subskill por estilo y sus
252
+ // commands (/design, /design-<estilo>) que leen specs/frontend/02-design.md.
253
+ kind: "library",
254
+ dir: "design",
255
+ required: false
256
+ }
257
+ ];
258
+ async function exists(path) {
259
+ try {
260
+ const { access } = await import("fs/promises");
261
+ await access(path);
262
+ return true;
263
+ } catch {
264
+ return false;
265
+ }
266
+ }
267
+ async function listMarkdown(dir) {
268
+ try {
269
+ const { readdir: readdir2 } = await import("fs/promises");
270
+ return (await readdir2(dir)).filter((f) => f.endsWith(".md"));
271
+ } catch {
272
+ return [];
273
+ }
274
+ }
275
+ async function planAgentBundle(repoRoot, bundle = AGENT_BUNDLE) {
276
+ const { join: join3 } = await import("path");
277
+ const installs = [];
278
+ const skillDirs = [];
279
+ const missingRequired = [];
280
+ for (const entry of bundle) {
281
+ const src = join3(repoRoot, "skills", entry.dir);
282
+ if (entry.kind === "library") {
283
+ if (!await exists(src)) {
284
+ if (entry.required) missingRequired.push(entry.dir);
285
+ continue;
286
+ }
287
+ installs.push({ from: src, to: `skills/${entry.dir}`, recursive: true });
288
+ const cmds = join3(src, "commands");
289
+ for (const file of await listMarkdown(cmds)) {
290
+ installs.push({ from: join3(cmds, file), to: `commands/${file}` });
291
+ }
292
+ continue;
293
+ }
294
+ const skill = join3(src, "SKILL.md");
295
+ const command = join3(src, "command.md");
296
+ if (!await exists(skill)) {
297
+ if (entry.required) missingRequired.push(entry.dir);
298
+ continue;
299
+ }
300
+ skillDirs.push(`skills/${entry.dir}`);
301
+ installs.push({ from: skill, to: `skills/${entry.dir}/SKILL.md` });
302
+ if (await exists(command)) {
303
+ installs.push({ from: command, to: `commands/${entry.dir}.md` });
304
+ } else if (entry.required) {
305
+ missingRequired.push(`${entry.dir}/command.md`);
306
+ }
307
+ for (const extra of entry.extras ?? []) {
308
+ const from = join3(src, extra);
309
+ if (await exists(from)) {
310
+ installs.push({
311
+ from,
312
+ to: `skills/${entry.dir}/${extra}`,
313
+ recursive: true
314
+ });
315
+ }
316
+ }
317
+ }
318
+ return { installs, skillDirs, commandDirs: ["commands"], missingRequired };
319
+ }
320
+
231
321
  // src/prompts.ts
232
322
  import inquirer from "inquirer";
233
323
  async function selectScope() {
@@ -417,7 +507,7 @@ function adaptScriptToNode(script) {
417
507
  return s;
418
508
  }
419
509
  async function adaptToNodeRuntime(projectPath) {
420
- const { readFile: readFile3, writeFile: writeFile2, readdir: readdir3 } = await import("fs/promises");
510
+ const { readFile: readFile3, writeFile: writeFile2, readdir: readdir2 } = await import("fs/promises");
421
511
  const { join: join3 } = await import("path");
422
512
  const pkgPath = join3(projectPath, "package.json");
423
513
  const pkg = JSON.parse(await readFile3(pkgPath, "utf-8"));
@@ -430,7 +520,7 @@ async function adaptToNodeRuntime(projectPath) {
430
520
  delete pkg.devDependencies["bun-types"];
431
521
  await writeFile2(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
432
522
  async function walk(dir) {
433
- const entries = await readdir3(dir, { withFileTypes: true });
523
+ const entries = await readdir2(dir, { withFileTypes: true });
434
524
  for (const entry of entries) {
435
525
  const full = join3(dir, entry.name);
436
526
  if (entry.isDirectory()) {
@@ -459,11 +549,11 @@ async function adaptToNodeRuntime(projectPath) {
459
549
  }
460
550
  }
461
551
  async function writeRootGitignore(projectPath) {
462
- const { writeFile: writeFile2, access: access2 } = await import("fs/promises");
552
+ const { writeFile: writeFile2, access } = await import("fs/promises");
463
553
  const { join: join3 } = await import("path");
464
554
  const gitignorePath = join3(projectPath, ".gitignore");
465
555
  try {
466
- await access2(gitignorePath);
556
+ await access(gitignorePath);
467
557
  } catch {
468
558
  await writeFile2(
469
559
  gitignorePath,
@@ -542,59 +632,29 @@ function getCliVersion() {
542
632
  }
543
633
  var AGENT_DIRS = [".opencode", ".pi"];
544
634
  async function installAgentSkills(repoRoot, projectPath) {
545
- const skill = join2(repoRoot, "skills", "create-specs", "SKILL.md");
546
- const command = join2(repoRoot, "skills", "create-specs", "command.md");
547
- const designDir = join2(repoRoot, "skills", "design");
548
- try {
549
- await access(skill);
550
- await access(command);
551
- } catch {
552
- return;
553
- }
635
+ const plan = await planAgentBundle(repoRoot);
554
636
  for (const agentDir of AGENT_DIRS) {
555
- const skillsDir = join2(projectPath, agentDir, "skills");
556
- const commandsDir = join2(projectPath, agentDir, "commands");
557
- const examplesDir = join2(repoRoot, "skills", "create-specs", "examples");
558
- let hasExamples = false;
559
- try {
560
- await access(examplesDir);
561
- hasExamples = true;
562
- } catch {
637
+ const root = join2(projectPath, agentDir);
638
+ for (const dir of plan.skillDirs) {
639
+ await mkdir2(join2(root, dir), { recursive: true });
563
640
  }
564
- let hasDesign = false;
565
- try {
566
- await access(designDir);
567
- hasDesign = true;
568
- } catch {
641
+ for (const dir of plan.commandDirs) {
642
+ await mkdir2(join2(root, dir), { recursive: true });
569
643
  }
570
- await mkdir2(join2(skillsDir, "create-specs"), { recursive: true });
571
- await mkdir2(commandsDir, { recursive: true });
572
- await cp2(skill, join2(skillsDir, "create-specs", "SKILL.md"));
573
- if (hasExamples) {
574
- await cp2(
575
- examplesDir,
576
- join2(skillsDir, "create-specs", "examples"),
577
- { recursive: true }
578
- );
579
- }
580
- await cp2(command, join2(commandsDir, "create-specs.md"));
581
- if (hasDesign) {
582
- await cp2(designDir, join2(skillsDir, "design"), { recursive: true });
583
- try {
584
- const designCommandsDir = join2(designDir, "commands");
585
- const commandFiles = (await readdir2(designCommandsDir)).filter(
586
- (f) => f.endsWith(".md")
587
- );
588
- for (const file of commandFiles) {
589
- await cp2(
590
- join2(designCommandsDir, file),
591
- join2(commandsDir, file)
592
- );
593
- }
594
- } catch {
595
- }
644
+ for (const item of plan.installs) {
645
+ await cp2(item.from, join2(root, item.to), {
646
+ recursive: item.recursive === true
647
+ });
596
648
  }
597
649
  }
650
+ if (plan.missingRequired.length > 0) {
651
+ console.log(
652
+ chalk.yellow(
653
+ ` Skills: el bundle remoto no trae ${plan.missingRequired.join(", ")} \u2014 omitido
654
+ `
655
+ )
656
+ );
657
+ }
598
658
  }
599
659
  async function readTemplatePostInit(dir) {
600
660
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fwinit",
3
- "version": "1.4.2",
3
+ "version": "1.4.3",
4
4
  "description": "CLI para crear proyectos desde templates",
5
5
  "type": "module",
6
6
  "bin": {