g360-cli 1.9.0 → 1.10.1

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.
@@ -4,7 +4,6 @@
4
4
  * Procesa archivos ERP y genera maestro_ventas_crm.csv
5
5
  */
6
6
 
7
- import { Command } from 'commander';
8
7
  import chalk from 'chalk';
9
8
  import path from 'path';
10
9
  import { fileURLToPath } from 'url';
@@ -13,87 +12,83 @@ import fs from 'fs-extra';
13
12
 
14
13
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
15
14
 
16
- export = (program: Command) => {
17
- program
18
- .command('ingest')
19
- .argument('<input>', 'Archivo CSV/Excel o directorio con archivos ERP')
20
- .option('-o, --output <archivo>', 'Ruta de salida', 'maestro_ventas_crm.csv')
21
- .action(async (input, options) => {
22
- console.log(chalk.blue(`\n🚀 Iniciando ingesta: ${input}`));
23
-
24
- const inputPath = path.resolve(input);
25
- const outputPath = path.resolve(options.output);
26
-
27
- // Determinar si es archivo o directorio
28
- try {
29
- await fs.access(inputPath);
30
- } catch {
31
- console.error(chalk.red(`❌ Ruta no encontrada: ${inputPath}`));
32
- process.exit(1);
33
- }
15
+ export async function ingest(input, options) {
16
+ const { output = 'maestro_ventas_crm.csv' } = options;
17
+
18
+ console.log(chalk.blue(`\n🚀 Iniciando ingesta: ${input}`));
34
19
 
35
- const stat = await fs.stat(inputPath);
36
- let filepaths: string[];
37
-
38
- if (stat.isFile()) {
39
- filepaths = [inputPath];
40
- } else if (stat.isDirectory()) {
41
- console.log(chalk.gray(`📁 Escaneando directorio...`));
42
- const { valid } = await scanDirectory(inputPath);
43
- filepaths = valid.map((info: any) => info.path);
44
- if (filepaths.length === 0) {
45
- console.error(chalk.red('❌ No se encontraron archivos ERP válidos en el directorio'));
46
- process.exit(1);
47
- }
48
- console.log(chalk.green(` Encontrados ${filepaths.length} archivos válidos`));
49
- } else {
50
- console.error(chalk.red(`❌ Ruta no válida: ${inputPath}`));
51
- process.exit(1);
52
- }
20
+ const inputPath = path.resolve(input);
21
+ const outputPath = path.resolve(output);
22
+
23
+ // Determinar si es archivo o directorio
24
+ try {
25
+ await fs.access(inputPath);
26
+ } catch {
27
+ console.error(chalk.red(`❌ Ruta no encontrada: ${inputPath}`));
28
+ process.exit(1);
29
+ }
30
+
31
+ const stat = await fs.stat(inputPath);
32
+ let filepaths;
33
+
34
+ if (stat.isFile()) {
35
+ filepaths = [inputPath];
36
+ } else if (stat.isDirectory()) {
37
+ console.log(chalk.gray(`📁 Escaneando directorio...`));
38
+ const result = await scanDirectory(inputPath);
39
+ filepaths = result.valid.map(info => info.path);
40
+ if (filepaths.length === 0) {
41
+ console.error(chalk.red('❌ No se encontraron archivos ERP válidos en el directorio'));
42
+ process.exit(1);
43
+ }
44
+ console.log(chalk.green(` Encontrados ${filepaths.length} archivos válidos`));
45
+ } else {
46
+ console.error(chalk.red(`❌ Ruta no válida: ${inputPath}`));
47
+ process.exit(1);
48
+ }
53
49
 
54
- // Procesar archivos
55
- console.log(chalk.blue(`\n⚙️ Procesando ${filepaths.length} archivo(s)...`));
56
-
57
- try {
58
- const combinedCsv = await runBatchIngest(filepaths);
59
-
60
- // Escribir salida
61
- await fs.ensureDir(path.dirname(outputPath));
62
- await fs.writeFile(outputPath, combinedCsv, 'utf-8');
63
- console.log(chalk.green(`\n✅ Ingesta completada: ${outputPath}`));
64
-
65
- // Resumen por archivo
66
- const lines = combinedCsv.split('\n');
67
- const header = lines[0];
68
- const dataLines = lines.filter(l => l && l !== header);
69
- console.log(chalk.gray(` Total filas: ${dataLines.length}`));
70
-
71
- // Conteo por ARCHIVO_ORIGEN
72
- const counts = new Map<string, number>();
73
- for (const line of dataLines) {
74
- const cols = line.split(',');
75
- const idx = header.split(',').indexOf('ARCHIVO_ORIGEN');
76
- if (idx !== -1 && cols[idx]) {
77
- counts.set(cols[idx], (counts.get(cols[idx]) || 0) + 1);
78
- }
79
- }
80
- if (counts.size > 0) {
81
- console.log(chalk.cyan('\n📊 Resumen por archivo:'));
82
- for (const [file, count] of counts) {
83
- console.log(` ${file}: ${count} filas`);
84
- }
85
- }
86
-
87
- } catch (err: any) {
88
- console.error(chalk.red('\n❌ Error durante la ingesta:'), err.message);
89
- if (err.stdout) console.error(chalk.gray(err.stdout));
90
- if (err.stderr) console.error(chalk.red(err.stderr));
91
- process.exit(1);
50
+ // Procesar archivos
51
+ console.log(chalk.blue(`\n⚙️ Procesando ${filepaths.length} archivo(s)...`));
52
+
53
+ try {
54
+ const combinedCsv = await runBatchIngest(filepaths);
55
+
56
+ // Escribir salida
57
+ await fs.ensureDir(path.dirname(outputPath));
58
+ await fs.writeFile(outputPath, combinedCsv, 'utf-8');
59
+ console.log(chalk.green(`\n✅ Ingesta completada: ${outputPath}`));
60
+
61
+ // Resumen por archivo
62
+ const lines = combinedCsv.split('\n');
63
+ const header = lines[0];
64
+ const dataLines = lines.filter(l => l && l !== header);
65
+ console.log(chalk.gray(` Total filas: ${dataLines.length}`));
66
+
67
+ // Conteo por ARCHIVO_ORIGEN
68
+ const counts = new Map();
69
+ for (const line of dataLines) {
70
+ const cols = line.split(',');
71
+ const idx = header.split(',').indexOf('ARCHIVO_ORIGEN');
72
+ if (idx !== -1 && cols[idx]) {
73
+ counts.set(cols[idx], (counts.get(cols[idx]) || 0) + 1);
92
74
  }
93
- });
94
- };
75
+ }
76
+ if (counts.size > 0) {
77
+ console.log(chalk.cyan('\n📊 Resumen por archivo:'));
78
+ for (const [file, count] of counts) {
79
+ console.log(` ${file}: ${count} filas`);
80
+ }
81
+ }
95
82
 
96
- async function scanDirectory(dir: string): Promise<{ valid: any[]; invalid: any[] }> {
83
+ } catch (err) {
84
+ console.error(chalk.red('\n❌ Error durante la ingesta:'), err.message);
85
+ if (err.stdout) console.error(chalk.gray(err.stdout));
86
+ if (err.stderr) console.error(chalk.red(err.stderr));
87
+ process.exit(1);
88
+ }
89
+ }
90
+
91
+ async function scanDirectory(dir) {
97
92
  const pyCode = `
98
93
  import sys
99
94
  sys.path.insert(0, '${path.join(__dirname, '..', 'py', 'src')}')
@@ -109,8 +104,8 @@ for i in invalid[:10]:
109
104
 
110
105
  const result = await runPython(pyCode);
111
106
  const lines = result.stdout.split('\n').filter(l => l.trim());
112
- const valid: any[] = [];
113
- const invalid: any[] = [];
107
+ const valid = [];
108
+ const invalid = [];
114
109
 
115
110
  for (const line of lines) {
116
111
  if (line.startsWith('VALID::')) {
@@ -125,7 +120,7 @@ for i in invalid[:10]:
125
120
  return { valid, invalid };
126
121
  }
127
122
 
128
- async function runBatchIngest(filepaths: string[]): Promise<string> {
123
+ async function runBatchIngest(filepaths) {
129
124
  const pyCode = `
130
125
  import sys
131
126
  sys.path.insert(0, '${path.join(__dirname, '..', 'py', 'src')}')
@@ -135,7 +130,6 @@ import pandas as pd
135
130
 
136
131
  filepaths = [${JSON.stringify(filepaths).replace(/"/g, "'")}]
137
132
  df = batch_process_files([Path(p) for p in filepaths], merge_results=True)
138
- # Output as CSV to stdout
139
133
  sys.stdout.write(df.to_csv(index=False))
140
134
  `;
141
135
 
@@ -165,7 +159,7 @@ sys.stdout.write(df.to_csv(index=False))
165
159
  });
166
160
  }
167
161
 
168
- function runPython(code: string): Promise<{ stdout: string; stderr: string }> {
162
+ function runPython(code) {
169
163
  return new Promise((resolve, reject) => {
170
164
  const pyExec = process.env.PYTHON || 'python3';
171
165
  const proc = spawn(pyExec, ['-c', code], {
@@ -190,4 +184,4 @@ function runPython(code: string): Promise<{ stdout: string; stderr: string }> {
190
184
  reject(new Error(`No se pudo ejecutar Python: ${err.message}`));
191
185
  });
192
186
  });
193
- }
187
+ }
@@ -40,10 +40,21 @@ export async function init(name, options) {
40
40
 
41
41
  const targetDir = path.join(process.cwd(), dir, name);
42
42
 
43
+ // Leer version del CLI desde package.json
44
+ const cliPkgPath = path.join(__dirname, '..', '..', 'package.json');
45
+ let cliVersion = '1.0.0';
46
+ try {
47
+ const cliPkg = fs.readJsonSync(cliPkgPath);
48
+ cliVersion = cliPkg.version || '1.0.0';
49
+ } catch {
50
+ // fallback si no se puede leer
51
+ }
52
+
43
53
  console.log(chalk.bold.cyan('\n🚀 G360 Project Initialization\n'));
44
54
  console.log(`Project: ${chalk.yellow(name)}`);
45
55
  console.log(`Template: ${chalk.blue(template)}`);
46
56
  console.log(`Skill: ${chalk.magenta(skill)}`);
57
+ console.log(`CLI Version: ${chalk.gray(cliVersion)}`);
47
58
  console.log(`Target: ${chalk.gray(targetDir)}\n`);
48
59
 
49
60
  let wantPortable = false;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * @file init.test.js
3
+ * @description Tests para el comando init
4
+ */
5
+
6
+ import { describe, it, expect } from 'vitest';
7
+
8
+ describe('init command', () => {
9
+ it('should export init function', async () => {
10
+ const mod = await import('../commands/init.js');
11
+ expect(mod).toHaveProperty('init');
12
+ expect(typeof mod.init).toBe('function');
13
+ });
14
+
15
+ it('should accept name and options parameters', async () => {
16
+ const { init } = await import('../commands/init.js');
17
+ expect(init.length).toBe(2);
18
+ });
19
+ });
@@ -0,0 +1,132 @@
1
+ /**
2
+ * @file list.test.js
3
+ * @description Tests para el comando list
4
+ */
5
+
6
+ import { describe, it, expect, beforeEach, vi } from 'vitest';
7
+ import fs from 'fs-extra';
8
+ import path from 'path';
9
+ import { fileURLToPath } from 'url';
10
+ import { list } from '../commands/list.js';
11
+
12
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
13
+
14
+ // Mock de fs-extra
15
+ vi.mock('fs-extra', () => ({
16
+ default: {
17
+ existsSync: vi.fn(),
18
+ readdirSync: vi.fn(),
19
+ readJsonSync: vi.fn()
20
+ }
21
+ }));
22
+
23
+ describe('list command', () => {
24
+ beforeEach(() => {
25
+ vi.clearAllMocks();
26
+ });
27
+
28
+ describe('list templates', () => {
29
+ it('should list available templates', async () => {
30
+ const mockTemplates = ['web-pwa', 'svelte-web', 'solid-web'];
31
+ fs.existsSync.mockReturnValue(true);
32
+ fs.readdirSync.mockReturnValue(mockTemplates);
33
+
34
+ await list('templates', { json: false });
35
+
36
+ expect(fs.readdirSync).toHaveBeenCalled();
37
+ });
38
+
39
+ it('should return empty array when templates directory does not exist', async () => {
40
+ fs.existsSync.mockReturnValue(false);
41
+
42
+ await list('templates', { json: false });
43
+
44
+ expect(fs.readdirSync).not.toHaveBeenCalled();
45
+ });
46
+ });
47
+
48
+ describe('list skills', () => {
49
+ it('should list skills from g360-skills.json', async () => {
50
+ const mockSkills = {
51
+ skills: [
52
+ { name: 'corporativo', description: 'Proyectos corporativos', device: 'pc' },
53
+ { name: 'moderno', description: 'Herramientas modernas', device: 'movil' }
54
+ ]
55
+ };
56
+
57
+ fs.existsSync.mockReturnValue(true);
58
+ fs.readJsonSync.mockReturnValue(mockSkills);
59
+
60
+ await list('skills', { json: false });
61
+
62
+ expect(fs.readJsonSync).toHaveBeenCalled();
63
+ });
64
+
65
+ it('should handle missing g360-skills.json gracefully', async () => {
66
+ fs.existsSync.mockReturnValue(false);
67
+
68
+ await list('skills', { json: false });
69
+
70
+ expect(fs.readJsonSync).not.toHaveBeenCalled();
71
+ });
72
+ });
73
+
74
+ describe('list snippets', () => {
75
+ it('should list snippets from snippets.json', async () => {
76
+ const mockSnippets = {
77
+ snippets: [
78
+ { name: 'cli-argparse-basic', description: 'Basic argparse CLI', language: 'python' },
79
+ { name: 'g360-button', description: 'G360 styled button', language: 'html' }
80
+ ]
81
+ };
82
+
83
+ fs.existsSync.mockReturnValue(true);
84
+ fs.readJsonSync.mockReturnValue(mockSnippets);
85
+
86
+ await list('snippets', { json: false });
87
+
88
+ expect(fs.readJsonSync).toHaveBeenCalled();
89
+ });
90
+
91
+ it('should handle missing snippets.json gracefully', async () => {
92
+ fs.existsSync.mockReturnValue(false);
93
+
94
+ await list('snippets', { json: false });
95
+
96
+ expect(fs.readJsonSync).not.toHaveBeenCalled();
97
+ });
98
+ });
99
+
100
+ describe('list all', () => {
101
+ it('should list all asset types', async () => {
102
+ const mockTemplates = ['web-pwa', 'svelte-web'];
103
+ const mockSkills = {
104
+ skills: [{ name: 'corporativo', description: 'Proyectos corporativos', device: 'pc' }]
105
+ };
106
+ const mockSnippets = {
107
+ snippets: [{ name: 'cli-argparse-basic', description: 'Basic argparse', language: 'python' }]
108
+ };
109
+
110
+ fs.existsSync.mockReturnValue(true);
111
+ fs.readdirSync.mockReturnValue(mockTemplates);
112
+ fs.readJsonSync.mockReturnValue(mockSkills).mockReturnValueOnce(mockSnippets);
113
+
114
+ await list('all', { json: false });
115
+
116
+ expect(fs.readdirSync).toHaveBeenCalled();
117
+ expect(fs.readJsonSync).toHaveBeenCalled();
118
+ });
119
+ });
120
+
121
+ describe('JSON output', () => {
122
+ it('should output JSON when requested', async () => {
123
+ const mockTemplates = ['web-pwa'];
124
+ fs.existsSync.mockReturnValue(true);
125
+ fs.readdirSync.mockReturnValue(mockTemplates);
126
+
127
+ await list('templates', { json: true });
128
+
129
+ expect(fs.readdirSync).toHaveBeenCalled();
130
+ });
131
+ });
132
+ });
@@ -4,99 +4,87 @@
4
4
  * Escanea un directorio para detectar archivos ERP válidos.
5
5
  */
6
6
 
7
- import { Command } from 'commander';
8
7
  import chalk from 'chalk';
9
8
  import path from 'path';
10
9
  import { fileURLToPath } from 'url';
10
+ import { spawn } from 'child_process';
11
11
 
12
12
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
13
13
 
14
- export = (program: Command) => {
15
- program
16
- .command('scan')
17
- .argument('<directorio>', 'Directorio a escanear')
18
- .option('-r, --recursive', 'Buscar recursivamente', true)
19
- .option('--min-score <n>', 'Puntuación mínima', '10')
20
- .action(async (directorio, options) => {
21
- console.log(chalk.blue(`\n🔍 Escaneando directorio: ${directorio}`));
14
+ export async function scan(directory, options) {
15
+ const { recursive = true, minScore = 10 } = options;
16
+
17
+ console.log(chalk.blue(`\n🔍 Escaneando directorio: ${directory}`));
22
18
 
23
- const pyCode = `
19
+ const pyCode = `
24
20
  import sys
21
+ sys.path.insert(0, '${path.join(__dirname, '..', 'py', 'src')}')
22
+ from g360_core.scanner import find_erp_files_in_dir
25
23
  from pathlib import Path
26
-
27
- from g360_core.scanner import ERPScanner
28
-
29
- scanner = ERPScanner(min_valid_score=${options.minScore})
30
- files = scanner.scan_directory(Path('${directorio}'), recursive=${options.recursive})
31
- valid = scanner.get_valid_files(files)
32
- invalid = scanner.get_invalid_files(files)
33
-
34
- print(f"TOTAL={len(files)}")
35
- print(f"VALIDOS={len(valid)}")
36
- print(f"INVALIDOS={len(invalid)}")
37
-
38
- for erp_type, flist in scanner.group_by_erp_type(files).items():
39
- if erp_type not in ('UNKNOWN', 'ERROR'):
40
- print(f"TIPO::{erp_type}::{len(flist)}")
41
- for info in flist[:5]:
42
- print(f"FILE::{info.path.name}::{info.size_bytes}")
43
-
44
- for info in invalid[:10]:
45
- print(f"INVALID::{info.path.name}::${info.error_msg}")
24
+ import json
25
+
26
+ valid, invalid = find_erp_files_in_dir(Path('${directory}'), recursive=${recursive})
27
+ result = {
28
+ "valid": [{"path": str(v.path), "erp_type": v.erp_type, "size_bytes": v.size_bytes} for v in valid],
29
+ "invalid": [{"path": str(i.path), "error_msg": i.error_msg} for i in invalid],
30
+ "stats": {"total": len(valid) + len(invalid), "valid": len(valid), "invalid": len(invalid)}
31
+ }
32
+ print(json.dumps(result, indent=2))
46
33
  `;
47
34
 
48
- try {
49
- const { runPython } = await import('../lib/python_runner.js');
50
- const result = await runPython(pyCode);
51
- const lines = result.stdout.split('\n').filter(l => l.trim());
52
-
53
- let total = 0, validCount = 0, invalidCount = 0;
54
- const types = new Map();
55
-
56
- for (const line of lines) {
57
- if (line.startsWith('TOTAL=')) total = parseInt(line.split('=')[1]);
58
- else if (line.startsWith('VALIDOS=')) validCount = parseInt(line.split('=')[1]);
59
- else if (line.startsWith('INVALIDOS=')) invalidCount = parseInt(line.split('=')[1]);
60
- else if (line.startsWith('TIPO::')) {
61
- const [, tipo, count] = line.split('::');
62
- types.set(tipo, parseInt(count));
63
- }
64
- }
65
-
66
- console.log(chalk.gray(`\n📈 Resultados:`));
67
- console.log(` Total archivos: ${total}`);
68
- console.log(chalk.green(` Válidos: ${validCount}`));
69
- console.log(chalk.red(` Inválidos/Error: ${invalidCount}`));
70
-
71
- if (types.size > 0) {
72
- console.log(chalk.green('\n✅ Archivos válidos por tipo ERP:'));
73
- for (const [tipo, count] of types) {
74
- console.log(` ${tipo}: ${count} archivos`);
75
- }
76
- }
35
+ try {
36
+ const result = await runPython(pyCode);
37
+ const data = JSON.parse(result.stdout);
38
+
39
+ console.log(chalk.gray(`\n Total archivos: ${data.stats.total}`));
40
+ console.log(chalk.green(` Válidos: ${data.stats.valid}`));
41
+ console.log(chalk.red(` Inválidos: ${data.stats.invalid}`));
42
+
43
+ if (data.valid.length > 0) {
44
+ console.log(chalk.cyan('\n📋 Archivos válidos:'));
45
+ data.valid.forEach(f => {
46
+ console.log(chalk.gray(` ${f.path} (${f.erp_type})`));
47
+ });
48
+ }
49
+
50
+ if (data.invalid.length > 0) {
51
+ console.log(chalk.yellow('\n⚠️ Archivos inválidos:'));
52
+ data.invalid.forEach(f => {
53
+ console.log(chalk.red(` ${f.path}: ${f.error_msg}`));
54
+ });
55
+ }
56
+
57
+ } catch (err) {
58
+ console.error(chalk.red(`\n❌ Error escaneando: ${err.message}`));
59
+ if (err.stdout) console.log(chalk.gray(err.stdout));
60
+ if (err.stderr) console.error(chalk.red(err.stderr));
61
+ process.exit(1);
62
+ }
63
+ }
64
+
65
+ function runPython(code) {
66
+ return new Promise((resolve, reject) => {
67
+ const pyExec = process.env.PYTHON || 'python3';
68
+ const proc = spawn(pyExec, ['-c', code], {
69
+ stdio: ['ignore', 'pipe', 'pipe'],
70
+ });
77
71
 
78
- if (invalidCount > 0) {
79
- console.log(chalk.red('\n❌ Archivos con problemas:'));
80
- let invalidLines = lines.filter(l => l.startsWith('INVALID::'));
81
- for (const line of invalidLines.slice(0, 10)) {
82
- const parts = line.split('::');
83
- console.log(` • ${parts[1]}: ${parts.slice(2).join('::')}`);
84
- }
85
- if (invalidLines.length > 10) {
86
- console.log(` ... y ${invalidLines.length - 10} más`);
87
- }
88
- }
72
+ let stdout = '';
73
+ let stderr = '';
89
74
 
90
- if (validCount > 0) {
91
- console.log(chalk.cyan('\n💡 Para procesar todos estos archivos:'));
92
- console.log(` g360 ingest "${directorio}" -o maestro_ventas_crm.csv`);
93
- }
75
+ proc.stdout?.on('data', (data) => { stdout += data.toString(); });
76
+ proc.stderr?.on('data', (data) => { stderr += data.toString(); });
94
77
 
95
- } catch (err: any) {
96
- console.error(chalk.red('❌ Error durante escaneo:'), err.message);
97
- if (err.stdout) console.error(chalk.gray(err.stdout));
98
- if (err.stderr) console.error(chalk.red(err.stderr));
99
- process.exit(1);
78
+ proc.on('close', (code) => {
79
+ if (code === 0) {
80
+ resolve({ stdout, stderr });
81
+ } else {
82
+ reject(new Error(stderr || `Python terminó con código ${code}`));
100
83
  }
101
84
  });
102
- };
85
+
86
+ proc.on('error', (err) => {
87
+ reject(new Error(`No se pudo ejecutar Python: ${err.message}`));
88
+ });
89
+ });
90
+ }
@@ -12,11 +12,13 @@ import { fileURLToPath } from 'url';
12
12
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
13
13
  const SKILLS_PATH = path.resolve(__dirname, '../assets/config/g360-skills.json');
14
14
 
15
- export async function setSkill(skillName, options) {
16
- const { verbose = false, cwd = process.cwd() } = options;
15
+ export async function setSkill(skillName, options = {}) {
16
+ const { verbose = false, cwd = process.cwd(), force = false } = options;
17
17
  const isInternalCall = options.cwd !== undefined;
18
18
 
19
- console.log(chalk.bold.cyan('\n🎨 G360 Skill Selector\n'));
19
+ if (!isInternalCall) {
20
+ console.log(chalk.bold.cyan('\n🎨 G360 Skill Selector\n'));
21
+ }
20
22
 
21
23
  // Cargar skills disponibles
22
24
  let skillsConfig;
@@ -43,9 +45,9 @@ export async function setSkill(skillName, options) {
43
45
  const skillJsonPath = path.join(cwd, 'skill.json');
44
46
 
45
47
  if (fs.existsSync(skillJsonPath)) {
46
- console.log(chalk.yellow('⚠️ El proyecto ya tiene un skill configurado.'));
47
- console.log(chalk.gray('Usar --force para sobrescribir'));
48
- if (!options.force) {
48
+ if (!force) {
49
+ console.log(chalk.yellow('⚠️ El proyecto ya tiene un skill configurado.'));
50
+ console.log(chalk.gray('Usar --force para sobrescribir'));
49
51
  console.log(chalk.cyan('\nPara cambiar el skill:'));
50
52
  console.log(chalk.cyan(' g360 set-skill ') + skillName + chalk.cyan(' --force'));
51
53
  return;
@@ -65,17 +67,19 @@ export async function setSkill(skillName, options) {
65
67
 
66
68
  await fs.writeJson(skillJsonPath, skillData, { spaces: 2 });
67
69
 
68
- console.log(chalk.green(`\n✅ Skill "${skillName}" configurado correctamente`));
69
- console.log(chalk.gray('\nDetalles:'));
70
- console.log(` Device: ${skill.device}`);
71
- console.log(` Accent: ${skill.colors.accent}`);
72
- console.log(` Signature: ${skill.signature.mode}`);
73
-
74
- if (verbose) {
75
- console.log(chalk.gray('\nColores:'));
76
- Object.entries(skill.colors).forEach(([key, value]) => {
77
- console.log(` ${key}: ${value}`);
78
- });
70
+ if (!isInternalCall) {
71
+ console.log(chalk.green(`\n✅ Skill "${skillName}" configurado correctamente`));
72
+ console.log(chalk.gray('\nDetalles:'));
73
+ console.log(` Device: ${skill.device}`);
74
+ console.log(` Accent: ${skill.colors.accent}`);
75
+ console.log(` Signature: ${skill.signature.mode}`);
76
+
77
+ if (verbose) {
78
+ console.log(chalk.gray('\nColores:'));
79
+ Object.entries(skill.colors).forEach(([key, value]) => {
80
+ console.log(` ${key}: ${value}`);
81
+ });
82
+ }
79
83
  }
80
84
 
81
85
  } catch (error) {