sincron-auto 1.0.0

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/bin/install.js ADDED
@@ -0,0 +1,449 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const os = require('os');
6
+ const readline = require('readline');
7
+
8
+ // Colors
9
+ const cyan = '\x1b[36m';
10
+ const green = '\x1b[32m';
11
+ const yellow = '\x1b[33m';
12
+ const dim = '\x1b[2m';
13
+ const reset = '\x1b[0m';
14
+
15
+ // Get version from package.json
16
+ const pkg = require('../package.json');
17
+
18
+ // Parse args
19
+ const args = process.argv.slice(2);
20
+ const hasGlobal = args.includes('--global') || args.includes('-g');
21
+ const hasLocal = args.includes('--local') || args.includes('-l');
22
+ const hasUninstall = args.includes('--uninstall') || args.includes('-u');
23
+ const hasHelp = args.includes('--help') || args.includes('-h');
24
+
25
+ // Parse --config-dir argument
26
+ function parseConfigDirArg() {
27
+ const configDirIndex = args.findIndex(arg => arg === '--config-dir' || arg === '-c');
28
+ if (configDirIndex !== -1) {
29
+ const nextArg = args[configDirIndex + 1];
30
+ if (!nextArg || nextArg.startsWith('-')) {
31
+ console.error(` ${yellow}--config-dir requires a path argument${reset}`);
32
+ process.exit(1);
33
+ }
34
+ return nextArg;
35
+ }
36
+ const configDirArg = args.find(arg => arg.startsWith('--config-dir=') || arg.startsWith('-c='));
37
+ if (configDirArg) {
38
+ const value = configDirArg.split('=')[1];
39
+ if (!value) {
40
+ console.error(` ${yellow}--config-dir requires a non-empty path${reset}`);
41
+ process.exit(1);
42
+ }
43
+ return value;
44
+ }
45
+ return null;
46
+ }
47
+ const explicitConfigDir = parseConfigDirArg();
48
+
49
+ const banner = `
50
+ ${cyan} ███████╗ ██╗ ███╗ ██╗ ██████╗ ██████╗ ██████╗ ███╗ ██╗
51
+ ██╔════╝ ██║ ████╗ ██║ ██╔════╝ ██╔══██╗ ██╔═══██╗ ████╗ ██║
52
+ ███████╗ ██║ ██╔██╗ ██║ ██║ ██████╔╝ ██║ ██║ ██╔██╗ ██║
53
+ ╚════██║ ██║ ██║╚██╗██║ ██║ ██╔══██╗ ██║ ██║ ██║╚██╗██║
54
+ ███████║ ██║ ██║ ╚████║ ╚██████╗ ██║ ██║ ╚██████╔╝ ██║ ╚████║
55
+ ╚══════╝ ╚═╝ ╚═╝ ╚═══╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝${reset}
56
+
57
+ ${green}Sincron-Auto${reset} ${dim}v${pkg.version}${reset}
58
+ Plugin de automação multi-agente para Claude Code.
59
+ Do planejamento à execução, com inteligência distribuída.
60
+ `;
61
+
62
+ console.log(banner);
63
+
64
+ // Show help if requested
65
+ if (hasHelp) {
66
+ console.log(` ${yellow}Uso:${reset} npx sincron-auto [opções]
67
+
68
+ ${yellow}Opções:${reset}
69
+ ${cyan}-g, --global${reset} Instalar globalmente (~/.claude)
70
+ ${cyan}-l, --local${reset} Instalar localmente (./.claude)
71
+ ${cyan}-u, --uninstall${reset} Desinstalar Sincron-Auto
72
+ ${cyan}-c, --config-dir <path>${reset} Especificar diretório de config
73
+ ${cyan}-h, --help${reset} Mostrar esta ajuda
74
+
75
+ ${yellow}Exemplos:${reset}
76
+ ${dim}# Instalação interativa${reset}
77
+ npx sincron-auto
78
+
79
+ ${dim}# Instalar globalmente${reset}
80
+ npx sincron-auto --global
81
+
82
+ ${dim}# Instalar apenas no projeto atual${reset}
83
+ npx sincron-auto --local
84
+
85
+ ${dim}# Desinstalar globalmente${reset}
86
+ npx sincron-auto --global --uninstall
87
+
88
+ ${yellow}Comandos disponíveis após instalação:${reset}
89
+ ${cyan}/sincron-auto${reset} - Iniciar workflow de automação multi-agente
90
+ `);
91
+ process.exit(0);
92
+ }
93
+
94
+ /**
95
+ * Expand ~ to home directory
96
+ */
97
+ function expandTilde(filePath) {
98
+ if (filePath && filePath.startsWith('~/')) {
99
+ return path.join(os.homedir(), filePath.slice(2));
100
+ }
101
+ return filePath;
102
+ }
103
+
104
+ /**
105
+ * Get the global config directory for Claude Code
106
+ */
107
+ function getGlobalDir(explicitDir = null) {
108
+ if (explicitDir) {
109
+ return expandTilde(explicitDir);
110
+ }
111
+ if (process.env.CLAUDE_CONFIG_DIR) {
112
+ return expandTilde(process.env.CLAUDE_CONFIG_DIR);
113
+ }
114
+ return path.join(os.homedir(), '.claude');
115
+ }
116
+
117
+ /**
118
+ * Read and parse settings.json
119
+ */
120
+ function readSettings(settingsPath) {
121
+ if (fs.existsSync(settingsPath)) {
122
+ try {
123
+ return JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
124
+ } catch (e) {
125
+ return {};
126
+ }
127
+ }
128
+ return {};
129
+ }
130
+
131
+ /**
132
+ * Write settings.json with proper formatting
133
+ */
134
+ function writeSettings(settingsPath, settings) {
135
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
136
+ }
137
+
138
+ /**
139
+ * Recursively copy directory, replacing paths in .md files
140
+ */
141
+ function copyWithPathReplacement(srcDir, destDir, pathPrefix) {
142
+ if (fs.existsSync(destDir)) {
143
+ fs.rmSync(destDir, { recursive: true });
144
+ }
145
+ fs.mkdirSync(destDir, { recursive: true });
146
+
147
+ const entries = fs.readdirSync(srcDir, { withFileTypes: true });
148
+
149
+ for (const entry of entries) {
150
+ const srcPath = path.join(srcDir, entry.name);
151
+ const destPath = path.join(destDir, entry.name);
152
+
153
+ if (entry.isDirectory()) {
154
+ copyWithPathReplacement(srcPath, destPath, pathPrefix);
155
+ } else if (entry.name.endsWith('.md')) {
156
+ let content = fs.readFileSync(srcPath, 'utf8');
157
+ content = content.replace(/~\/\.claude\//g, pathPrefix);
158
+ fs.writeFileSync(destPath, content);
159
+ } else {
160
+ fs.copyFileSync(srcPath, destPath);
161
+ }
162
+ }
163
+ }
164
+
165
+ /**
166
+ * Verify a directory exists and contains files
167
+ */
168
+ function verifyInstalled(dirPath, description) {
169
+ if (!fs.existsSync(dirPath)) {
170
+ console.error(` ${yellow}✗${reset} Falha ao instalar ${description}: diretório não criado`);
171
+ return false;
172
+ }
173
+ try {
174
+ const entries = fs.readdirSync(dirPath);
175
+ if (entries.length === 0) {
176
+ console.error(` ${yellow}✗${reset} Falha ao instalar ${description}: diretório vazio`);
177
+ return false;
178
+ }
179
+ } catch (e) {
180
+ console.error(` ${yellow}✗${reset} Falha ao instalar ${description}: ${e.message}`);
181
+ return false;
182
+ }
183
+ return true;
184
+ }
185
+
186
+ /**
187
+ * Uninstall Sincron-Auto from the specified directory
188
+ */
189
+ function uninstall(isGlobal) {
190
+ const targetDir = isGlobal
191
+ ? getGlobalDir(explicitConfigDir)
192
+ : path.join(process.cwd(), '.claude');
193
+
194
+ const locationLabel = isGlobal
195
+ ? targetDir.replace(os.homedir(), '~')
196
+ : targetDir.replace(process.cwd(), '.');
197
+
198
+ console.log(` Desinstalando Sincron-Auto de ${cyan}${locationLabel}${reset}\n`);
199
+
200
+ if (!fs.existsSync(targetDir)) {
201
+ console.log(` ${yellow}⚠${reset} Diretório não existe: ${locationLabel}`);
202
+ console.log(` Nada para desinstalar.\n`);
203
+ return;
204
+ }
205
+
206
+ let removedCount = 0;
207
+
208
+ // 1. Remove sincron-auto command
209
+ const commandsDir = path.join(targetDir, 'commands');
210
+ if (fs.existsSync(commandsDir)) {
211
+ const cmdPath = path.join(commandsDir, 'sincron-auto.md');
212
+ if (fs.existsSync(cmdPath)) {
213
+ fs.unlinkSync(cmdPath);
214
+ removedCount++;
215
+ console.log(` ${green}✓${reset} Removido comando sincron-auto`);
216
+ }
217
+ }
218
+
219
+ // 2. Remove sincron-auto agents
220
+ const agentsDir = path.join(targetDir, 'agents');
221
+ if (fs.existsSync(agentsDir)) {
222
+ const agentsToRemove = ['orquestrador.md', 'avaliador.md', 'construtor.md', 'estrutura.md', 'gestor-projeto.md'];
223
+ let agentCount = 0;
224
+ for (const agent of agentsToRemove) {
225
+ const agentPath = path.join(agentsDir, agent);
226
+ if (fs.existsSync(agentPath)) {
227
+ fs.unlinkSync(agentPath);
228
+ agentCount++;
229
+ }
230
+ }
231
+ if (agentCount > 0) {
232
+ removedCount++;
233
+ console.log(` ${green}✓${reset} Removidos ${agentCount} agentes`);
234
+ }
235
+ }
236
+
237
+ // 3. Remove sincron-auto skills
238
+ const skillsDir = path.join(targetDir, 'skills');
239
+ if (fs.existsSync(skillsDir)) {
240
+ const skillsToRemove = ['autorizacao', 'avaliacao', 'gerenciar', 'instalar-mcp', 'projetar', 'relatorio-final'];
241
+ let skillCount = 0;
242
+ for (const skill of skillsToRemove) {
243
+ const skillPath = path.join(skillsDir, skill);
244
+ if (fs.existsSync(skillPath)) {
245
+ fs.rmSync(skillPath, { recursive: true });
246
+ skillCount++;
247
+ }
248
+ }
249
+ if (skillCount > 0) {
250
+ removedCount++;
251
+ console.log(` ${green}✓${reset} Removidas ${skillCount} skills`);
252
+ }
253
+ }
254
+
255
+ // 4. Remove sincron-auto templates/docs
256
+ const sincronAutoDir = path.join(targetDir, 'sincron-auto');
257
+ if (fs.existsSync(sincronAutoDir)) {
258
+ fs.rmSync(sincronAutoDir, { recursive: true });
259
+ removedCount++;
260
+ console.log(` ${green}✓${reset} Removido sincron-auto/`);
261
+ }
262
+
263
+ if (removedCount === 0) {
264
+ console.log(` ${yellow}⚠${reset} Nenhum arquivo do Sincron-Auto encontrado.`);
265
+ }
266
+
267
+ console.log(`
268
+ ${green}Pronto!${reset} Sincron-Auto foi desinstalado.
269
+ Seus outros arquivos e configurações foram preservados.
270
+ `);
271
+ }
272
+
273
+ /**
274
+ * Install Sincron-Auto to the specified directory
275
+ */
276
+ function install(isGlobal) {
277
+ const src = path.join(__dirname, '..');
278
+ const targetDir = isGlobal
279
+ ? getGlobalDir(explicitConfigDir)
280
+ : path.join(process.cwd(), '.claude');
281
+
282
+ const locationLabel = isGlobal
283
+ ? targetDir.replace(os.homedir(), '~')
284
+ : targetDir.replace(process.cwd(), '.');
285
+
286
+ const pathPrefix = isGlobal
287
+ ? `${targetDir}/`
288
+ : './.claude/';
289
+
290
+ console.log(` Instalando em ${cyan}${locationLabel}${reset}\n`);
291
+
292
+ const failures = [];
293
+
294
+ // 1. Create directories
295
+ fs.mkdirSync(path.join(targetDir, 'commands'), { recursive: true });
296
+ fs.mkdirSync(path.join(targetDir, 'agents'), { recursive: true });
297
+ fs.mkdirSync(path.join(targetDir, 'skills'), { recursive: true });
298
+
299
+ // 2. Copy command
300
+ const commandsSrc = path.join(src, 'commands');
301
+ if (fs.existsSync(commandsSrc)) {
302
+ const commandFiles = fs.readdirSync(commandsSrc).filter(f => f.endsWith('.md'));
303
+ for (const file of commandFiles) {
304
+ let content = fs.readFileSync(path.join(commandsSrc, file), 'utf8');
305
+ content = content.replace(/~\/\.claude\//g, pathPrefix);
306
+ fs.writeFileSync(path.join(targetDir, 'commands', file), content);
307
+ }
308
+ console.log(` ${green}✓${reset} Instalado ${commandFiles.length} comando(s)`);
309
+ } else {
310
+ failures.push('commands');
311
+ }
312
+
313
+ // 3. Copy agents
314
+ const agentsSrc = path.join(src, 'agents');
315
+ if (fs.existsSync(agentsSrc)) {
316
+ const agentFiles = fs.readdirSync(agentsSrc).filter(f => f.endsWith('.md'));
317
+ for (const file of agentFiles) {
318
+ let content = fs.readFileSync(path.join(agentsSrc, file), 'utf8');
319
+ content = content.replace(/~\/\.claude\//g, pathPrefix);
320
+ fs.writeFileSync(path.join(targetDir, 'agents', file), content);
321
+ }
322
+ console.log(` ${green}✓${reset} Instalados ${agentFiles.length} agentes`);
323
+ } else {
324
+ failures.push('agents');
325
+ }
326
+
327
+ // 4. Copy skills
328
+ const skillsSrc = path.join(src, 'skills');
329
+ if (fs.existsSync(skillsSrc)) {
330
+ const skillDirs = fs.readdirSync(skillsSrc, { withFileTypes: true }).filter(d => d.isDirectory());
331
+ for (const dir of skillDirs) {
332
+ const srcSkillDir = path.join(skillsSrc, dir.name);
333
+ const destSkillDir = path.join(targetDir, 'skills', dir.name);
334
+ copyWithPathReplacement(srcSkillDir, destSkillDir, pathPrefix);
335
+ }
336
+ console.log(` ${green}✓${reset} Instaladas ${skillDirs.length} skills`);
337
+ } else {
338
+ failures.push('skills');
339
+ }
340
+
341
+ // 5. Copy templates to sincron-auto directory
342
+ const templatesSrc = path.join(src, 'templates');
343
+ const templatesDest = path.join(targetDir, 'sincron-auto', 'templates');
344
+ if (fs.existsSync(templatesSrc)) {
345
+ copyWithPathReplacement(templatesSrc, templatesDest, pathPrefix);
346
+ if (verifyInstalled(templatesDest, 'templates')) {
347
+ console.log(` ${green}✓${reset} Instalados templates`);
348
+ } else {
349
+ failures.push('templates');
350
+ }
351
+ }
352
+
353
+ // 6. Copy docs
354
+ const docsSrc = path.join(src, 'docs');
355
+ const docsDest = path.join(targetDir, 'sincron-auto', 'docs');
356
+ if (fs.existsSync(docsSrc)) {
357
+ copyWithPathReplacement(docsSrc, docsDest, pathPrefix);
358
+ if (verifyInstalled(docsDest, 'docs')) {
359
+ console.log(` ${green}✓${reset} Instalada documentação`);
360
+ } else {
361
+ failures.push('docs');
362
+ }
363
+ }
364
+
365
+ // 7. Write VERSION file
366
+ const versionDest = path.join(targetDir, 'sincron-auto', 'VERSION');
367
+ fs.mkdirSync(path.dirname(versionDest), { recursive: true });
368
+ fs.writeFileSync(versionDest, pkg.version);
369
+ console.log(` ${green}✓${reset} Escrito VERSION (${pkg.version})`);
370
+
371
+ if (failures.length > 0) {
372
+ console.error(`\n ${yellow}Instalação incompleta!${reset} Falhou: ${failures.join(', ')}`);
373
+ process.exit(1);
374
+ }
375
+
376
+ console.log(`
377
+ ${green}Pronto!${reset} Abra o Claude Code e execute ${cyan}/sincron-auto${reset}.
378
+
379
+ ${cyan}Comando disponível:${reset}
380
+ /sincron-auto - Iniciar workflow de automação multi-agente
381
+
382
+ ${cyan}Dica:${reset} Sincron-Auto funciona junto com Sincron-Plan!
383
+ Use /sincron-plan para planejar e /sincron-auto para executar.
384
+
385
+ ${cyan}Documentação:${reset} https://github.com/MLTCorp/sincron-auto
386
+ `);
387
+ }
388
+
389
+ /**
390
+ * Prompt for install location
391
+ */
392
+ function promptLocation() {
393
+ if (!process.stdin.isTTY) {
394
+ console.log(` ${yellow}Terminal não-interativo detectado, usando instalação global${reset}\n`);
395
+ install(true);
396
+ return;
397
+ }
398
+
399
+ const rl = readline.createInterface({
400
+ input: process.stdin,
401
+ output: process.stdout
402
+ });
403
+
404
+ let answered = false;
405
+
406
+ rl.on('close', () => {
407
+ if (!answered) {
408
+ answered = true;
409
+ console.log(`\n ${yellow}Instalação cancelada${reset}\n`);
410
+ process.exit(0);
411
+ }
412
+ });
413
+
414
+ const globalPath = getGlobalDir(explicitConfigDir).replace(os.homedir(), '~');
415
+
416
+ console.log(` ${yellow}Onde você quer instalar?${reset}
417
+
418
+ ${cyan}1${reset}) Global ${dim}(${globalPath})${reset} - disponível em todos os projetos
419
+ ${cyan}2${reset}) Local ${dim}(./.claude)${reset} - apenas neste projeto
420
+ `);
421
+
422
+ rl.question(` Escolha ${dim}[1]${reset}: `, (answer) => {
423
+ answered = true;
424
+ rl.close();
425
+ const choice = answer.trim() || '1';
426
+ const isGlobal = choice !== '2';
427
+ install(isGlobal);
428
+ });
429
+ }
430
+
431
+ // Main
432
+ if (hasGlobal && hasLocal) {
433
+ console.error(` ${yellow}Não é possível especificar --global e --local juntos${reset}`);
434
+ process.exit(1);
435
+ } else if (explicitConfigDir && hasLocal) {
436
+ console.error(` ${yellow}Não é possível usar --config-dir com --local${reset}`);
437
+ process.exit(1);
438
+ } else if (hasUninstall) {
439
+ if (!hasGlobal && !hasLocal) {
440
+ console.error(` ${yellow}--uninstall requer --global ou --local${reset}`);
441
+ console.error(` Exemplo: npx sincron-auto --global --uninstall`);
442
+ process.exit(1);
443
+ }
444
+ uninstall(hasGlobal);
445
+ } else if (hasGlobal || hasLocal) {
446
+ install(hasGlobal);
447
+ } else {
448
+ promptLocation();
449
+ }