@ronaldjdevfs/forge 1.1.0 → 1.3.0-beta

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/src/cli.js CHANGED
@@ -1,13 +1,23 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { copyFileSync, mkdirSync, existsSync, readdirSync, statSync, writeFileSync, readFileSync } from "fs";
3
+ import { copyFileSync, mkdirSync, existsSync, writeFileSync, readFileSync, cpSync, readdirSync } from "fs";
4
4
  import { join, dirname, relative } from "path";
5
5
  import { fileURLToPath } from "url";
6
6
  import { execSync } from "child_process";
7
+ import { spinner, log } from "@clack/prompts";
8
+ import { runWizard } from "./wizard.mjs";
7
9
 
8
10
  const __dirname = dirname(fileURLToPath(import.meta.url));
9
- const PKG_DIR = join(__dirname, "..");
10
- const SKILL_SRC = join(PKG_DIR, "skills", "forge");
11
+ const SKILL_SRC = join(__dirname, "..", "skills", "forge");
12
+ const AGENTS_TEMPLATES = join(SKILL_SRC, "templates", "agents");
13
+
14
+ const AGENT_CONFIGS = {
15
+ claude: { dir: ".claude", template: "claude", skillPath: ".claude/skills/forge", need: ["CLAUDE.md", "settings.local.json"], postInstall: "forgeSentinel" },
16
+ cursor: { dir: ".cursor", template: "cursor", skillPath: ".cursor/skills/forge", need: [".cursorrules", "hooks.json"], postInstall: "forgeSmith" },
17
+ codex: { dir: ".agents", template: "codex", skillPath: ".agents/skills/forge", need: ["hooks.json"], postInstall: "forgeSentinel" },
18
+ gemini: { dir: ".gemini", template: "gemini", skillPath: ".gemini/skills/forge", need: ["SKILL.md"], postInstall: null },
19
+ agents: { dir: ".agents", template: "agents", skillPath: ".agents/skills/forge", need: ["hooks.json"], postInstall: "forgeSentinel" },
20
+ };
11
21
 
12
22
  function getTargetDir(global) {
13
23
  if (global) {
@@ -30,16 +40,7 @@ function getConfigDir(global) {
30
40
  }
31
41
 
32
42
  function copyRecursive(src, dest) {
33
- mkdirSync(dest, { recursive: true });
34
- for (const entry of readdirSync(src)) {
35
- const srcPath = join(src, entry);
36
- const destPath = join(dest, entry);
37
- if (statSync(srcPath).isDirectory()) {
38
- copyRecursive(srcPath, destPath);
39
- } else {
40
- copyFileSync(srcPath, destPath);
41
- }
42
- }
43
+ cpSync(src, dest, { recursive: true, force: true });
43
44
  }
44
45
 
45
46
  function detectPM(configDir) {
@@ -62,7 +63,6 @@ function ensureDependencies(configDir) {
62
63
  if (!pkg.dependencies["@opencode-ai/plugin"]) {
63
64
  pkg.dependencies["@opencode-ai/plugin"] = "1.17.9";
64
65
  writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
65
- console.log(` ✓ Actualizado ${relative(process.cwd(), pkgPath)}`);
66
66
  }
67
67
 
68
68
  const pm = detectPM(configDir);
@@ -74,30 +74,28 @@ function ensureDependencies(configDir) {
74
74
 
75
75
  try {
76
76
  runPM(pm);
77
- console.log(` ✓ Dependencias instaladas (${pm})`);
78
77
  } catch {
79
78
  for (const fallback of order[pm]) {
80
79
  try {
81
80
  runPM(fallback);
82
- console.log(` ✓ Dependencias instaladas (${fallback})`);
83
81
  return;
84
- } catch {}
82
+ } catch { }
85
83
  }
86
- console.warn("No se pudieron instalar dependencias. Ejecuta 'pnpm install' | 'npm install' manualmente en " + configDir);
84
+ log.warn("No se pudieron instalar dependencias. Ejecutá 'pnpm install' | 'npm install' manualmente en " + configDir);
87
85
  }
88
86
  }
89
87
 
90
88
  const COMMANDS = [
91
- { name: "forge-forge", desc: "Forge — inicializar proyecto arquitectónicamente" },
92
- { name: "forge-cast", desc: "Cast — crear un nuevo feature hexagonal desde cero" },
89
+ { name: "forge-forge", desc: "Forge — inicializar proyecto arquitectónicamente" },
90
+ { name: "forge-cast", desc: "Cast — crear un nuevo feature hexagonal desde cero" },
93
91
  { name: "forge-inspect", desc: "Inspect — inspeccionar la conformidad arquitectónica" },
94
- { name: "forge-relocate",desc: "Relocate — migrar feature legacy a la nueva estructura" },
92
+ { name: "forge-relocate", desc: "Relocate — migrar feature legacy a la nueva estructura" },
95
93
  { name: "forge-reforge", desc: "Reforge — refactorizar la arquitectura de un feature" },
96
- { name: "forge-quench", desc: "Quench — verificar reglas arquitectónicas del proyecto" },
97
- { name: "forge-temper", desc: "Temper — endurecer la arquitectura (DI, seguridad)" },
98
- { name: "forge-chain", desc: "Chain — analizar cadena de dependencias entre features" },
99
- { name: "forge-inscribe",desc: "Inscribe — generar y mantener ARCHITECTURE.md" },
100
- { name: "forge-smelt", desc: "Smelt — extraer código reutilizable a shared/" },
94
+ { name: "forge-quench", desc: "Quench — verificar reglas arquitectónicas del proyecto" },
95
+ { name: "forge-temper", desc: "Temper — endurecer la arquitectura (DI, seguridad)" },
96
+ { name: "forge-chain", desc: "Chain — analizar cadena de dependencias entre features" },
97
+ { name: "forge-inscribe", desc: "Inscribe — generar y mantener ARCHITECTURE.md" },
98
+ { name: "forge-smelt", desc: "Smelt — extraer código reutilizable a shared/" },
101
99
  ];
102
100
 
103
101
  function generateCommands(configDir) {
@@ -116,46 +114,159 @@ function generateCommands(configDir) {
116
114
  ].join("\n");
117
115
  writeFileSync(join(cmdsDir, `${cmd.name}.md`), content);
118
116
  }
119
- console.log(` ✓ Comandos /forge-* generados en .opencode/commands/`);
120
117
  }
121
118
 
122
- function printHelp() {
123
- console.log(`
124
- Forge — Architecture OS
125
-
126
- USO
127
- forge install Instalar skill en el proyecto actual
128
- forge install --global Instalar globalmente (~/.config/opencode/)
129
- forge skills install (alias del comando install)
119
+ // --- Direct installers ---
130
120
 
131
- OPCIONES
132
- -g, --global Instalar en ~/.config/opencode/skills/forge/
133
- -h, --help Mostrar esta ayuda
134
- `);
135
- }
136
-
137
- function install(global) {
121
+ async function installOpenCode(isGlobal = false) {
138
122
  if (!existsSync(SKILL_SRC)) {
139
- console.error("[ERROR] No se encuentra skills/forge/ en el paquete. ¿Corrupto?");
123
+ log.error("No se encuentra skills/forge/ en el paquete. ¿Corrupto?");
140
124
  process.exit(1);
141
125
  }
142
126
 
143
- const target = getTargetDir(global);
144
- const configDir = getConfigDir(global);
127
+ const target = getTargetDir(isGlobal);
128
+ const configDir = getConfigDir(isGlobal);
129
+ const rel = relative(process.cwd(), target);
145
130
 
146
- console.log(`\n Forge Architecture OS`);
147
- console.log(` ${"=".repeat(40)}`);
148
- console.log(` Destino: ${target}\n`);
131
+ const s = spinner();
149
132
 
133
+ s.start("Copiando skill a " + rel);
150
134
  copyRecursive(SKILL_SRC, target);
151
- console.log(` ✓ Skill copiada a ${relative(process.cwd(), target)}`);
135
+ s.stop("Skill copiada a " + rel);
136
+
137
+ s.start("Generando comandos /forge-*");
152
138
  generateCommands(configDir);
139
+ s.stop("Comandos generados en " + relative(process.cwd(), join(configDir, "commands")));
140
+
141
+ s.start("Instalando dependencias");
153
142
  ensureDependencies(configDir);
154
- console.log(`\n ✓ Forge instalado correctamente`);
155
- console.log(` ${"=".repeat(40)}\n`);
143
+ s.stop("Dependencias instaladas");
144
+
145
+ log.success("OpenCode configurado correctamente");
146
+ }
147
+
148
+ async function installAgentTemplates(agentDir, agentName) {
149
+ const config = AGENT_CONFIGS[agentName];
150
+ if (!config) return;
151
+
152
+ const templateDir = join(AGENTS_TEMPLATES, config.template);
153
+ if (!existsSync(templateDir)) {
154
+ log.error(`Template para ${agentName} no encontrado en ${templateDir}`);
155
+ return;
156
+ }
157
+
158
+ const s = spinner();
159
+ s.start(`Copiando templates para ${agentName} en ${agentDir}/`);
160
+
161
+ mkdirSync(agentDir, { recursive: true });
162
+
163
+ // Copy skill into <agentDir>/skills/forge/
164
+ const skillDest = join(agentDir, "skills", "forge");
165
+ cpSync(SKILL_SRC, skillDest, { recursive: true, force: true });
166
+
167
+ // Copy template files (hooks, CLAUDE.md, .cursorrules, etc.)
168
+ for (const entry of readdirSync(templateDir)) {
169
+ const srcFile = join(templateDir, entry);
170
+ const destFile = join(agentDir, entry);
171
+ cpSync(srcFile, destFile, { recursive: true, force: true });
172
+ }
173
+
174
+ // Render SKILL.md with agent-specific path
175
+ const templatePath = join(AGENTS_TEMPLATES, "SKILL.md.template");
176
+ if (existsSync(templatePath)) {
177
+ const template = readFileSync(templatePath, "utf-8");
178
+ const rendered = template.replace(/\{\{AGENT_PATH\}\}/g, config.skillPath);
179
+ writeFileSync(join(skillDest, "SKILL.md"), rendered, "utf-8");
180
+ }
181
+
182
+ s.stop(`${agentName} configurado en ${agentDir}/`);
183
+ log.success(`${agentName} listo (skill + ${config.postInstall || "templates"})`);
184
+ }
185
+
186
+ async function installCursor() {
187
+ await installAgentTemplates(join(process.cwd(), ".cursor"), "cursor");
188
+ }
189
+
190
+ async function installClaude() {
191
+ await installAgentTemplates(join(process.cwd(), ".claude"), "claude");
192
+ }
193
+
194
+ async function installCodex() {
195
+ // Codex hooks point to .agents/skills/forge/ via git root
196
+ await installAgentTemplates(join(process.cwd(), ".agents"), "codex");
197
+ // Also copy hooks.json into .codex/ so Codex CLI picks it up
198
+ const codexDir = join(process.cwd(), ".codex");
199
+ mkdirSync(codexDir, { recursive: true });
200
+ const hooksSrc = join(AGENTS_TEMPLATES, "codex", "hooks.json");
201
+ if (existsSync(hooksSrc)) {
202
+ cpSync(hooksSrc, join(codexDir, "hooks.json"), { force: true });
203
+ }
204
+ }
205
+
206
+ async function installGemini() {
207
+ await installAgentTemplates(join(process.cwd(), ".gemini"), "gemini");
208
+ }
209
+
210
+ async function installAgentsGeneric() {
211
+ await installAgentTemplates(join(process.cwd(), ".agents"), "agents");
212
+ }
213
+
214
+ async function installAgents(selected, isGlobal = false) {
215
+ for (const agent of selected) {
216
+ switch (agent) {
217
+ case "opencode":
218
+ await installOpenCode(isGlobal);
219
+ break;
220
+ case "cursor":
221
+ await installCursor();
222
+ break;
223
+ case "claude":
224
+ await installClaude();
225
+ break;
226
+ case "codex":
227
+ await installCodex();
228
+ break;
229
+ case "gemini":
230
+ await installGemini();
231
+ break;
232
+ case "agents":
233
+ await installAgentsGeneric();
234
+ break;
235
+ }
236
+ }
237
+ }
238
+
239
+ // --- CLI ---
240
+
241
+ function printHelp() {
242
+ console.log(`
243
+ ⚔️ Forge — Architecture OS
244
+
245
+ USO
246
+ forge install Mostrar wizard interactivo de instalación
247
+ forge install --opencode Instalar solo para OpenCode
248
+ forge install --cursor Instalar solo para Cursor
249
+ forge install --claude Instalar solo para Claude Code
250
+ forge install --codex Instalar solo para Codex CLI
251
+ forge install --gemini Instalar solo para Gemini Code Assist
252
+ forge install --all Instalar para todos los agentes detectados
253
+ forge install --global Instalar OpenCode globalmente (~/.config/opencode/)
254
+ forge install --help Mostrar esta ayuda
255
+
256
+ AGENTES SOPORTADOS
257
+ OpenCode, Claude Code, Cursor, Codex CLI, Gemini Code Assist, Agentes Genéricos
258
+
259
+ HOOKS
260
+ forgeSentinel (PostToolUse) — guardia arquitectónico post-escritura
261
+ forgeSmith (preToolUse) — guardia arquitectónico pre-escritura (Cursor)
262
+
263
+ OPCIONES
264
+ -g, --global Instalar OpenCode en ~/.config/opencode/skills/forge/
265
+ -h, --help Mostrar esta ayuda
266
+ `);
156
267
  }
157
268
 
158
- function main() {
269
+ async function main() {
159
270
  const args = process.argv.slice(2);
160
271
  const isHelp = args.includes("-h") || args.includes("--help");
161
272
  const isGlobal = args.includes("-g") || args.includes("--global");
@@ -165,15 +276,37 @@ function main() {
165
276
  return;
166
277
  }
167
278
 
279
+ const hasOpenCode = args.includes("--opencode");
280
+ const hasCursor = args.includes("--cursor");
281
+ const hasClaude = args.includes("--claude");
282
+ const hasCodex = args.includes("--codex");
283
+ const hasGemini = args.includes("--gemini");
284
+ const hasAll = args.includes("--all");
285
+
168
286
  const subcommandIndex = args.indexOf("skills");
169
287
  const command = subcommandIndex !== -1 ? args[subcommandIndex + 1] : args[0];
170
288
 
171
289
  if (command === "install") {
172
- install(isGlobal);
290
+ if (hasAll) {
291
+ await installAgents(["opencode", "cursor", "claude", "codex", "gemini", "agents"], isGlobal);
292
+ } else if (hasOpenCode || hasCursor || hasClaude || hasCodex || hasGemini) {
293
+ const selected = [];
294
+ if (hasOpenCode) selected.push("opencode");
295
+ if (hasCursor) selected.push("cursor");
296
+ if (hasClaude) selected.push("claude");
297
+ if (hasCodex) selected.push("codex");
298
+ if (hasGemini) selected.push("gemini");
299
+ await installAgents(selected, isGlobal);
300
+ } else {
301
+ await runWizard();
302
+ }
173
303
  } else {
174
304
  printHelp();
175
305
  process.exit(1);
176
306
  }
177
307
  }
178
308
 
179
- main();
309
+ main().catch((err) => {
310
+ console.error("[ERROR]", err.message);
311
+ process.exit(1);
312
+ });