@korl3one/ccode 2.3.0 → 3.1.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/README.md +81 -46
- package/dist/ai/claude.d.ts +2 -1
- package/dist/ai/claude.js +15 -12
- package/dist/ai/gemini.d.ts +13 -1
- package/dist/ai/gemini.js +62 -15
- package/dist/ai/manager.d.ts +25 -6
- package/dist/ai/manager.js +83 -21
- package/dist/cli/brand.js +1 -1
- package/dist/cli/index.js +252 -96
- package/dist/core/exports.d.ts +74 -0
- package/dist/core/exports.js +303 -0
- package/package.json +9 -4
- package/dist/ai/deepseek.d.ts +0 -11
- package/dist/ai/deepseek.js +0 -35
- package/dist/ai/groq.d.ts +0 -11
- package/dist/ai/groq.js +0 -35
- package/dist/ai/ollama.d.ts +0 -10
- package/dist/ai/ollama.js +0 -27
- package/dist/ai/openai.d.ts +0 -10
- package/dist/ai/openai.js +0 -34
package/dist/cli/index.js
CHANGED
|
@@ -10,7 +10,9 @@ import { FileUtils } from '../utils/files.js';
|
|
|
10
10
|
import { ContextEngine } from '../core/context.js';
|
|
11
11
|
import { TaskEngine } from '../core/tasks.js';
|
|
12
12
|
import { PromptBuilder } from '../core/prompt-builder.js';
|
|
13
|
-
import { AIManager } from '../ai/manager.js';
|
|
13
|
+
import { AIManager, PROVIDER_INFO } from '../ai/manager.js';
|
|
14
|
+
import { ContextExporter } from '../core/exports.js';
|
|
15
|
+
import { exec } from 'child_process';
|
|
14
16
|
import { FileWatcher, displayChanges } from './watcher.js';
|
|
15
17
|
// ─── Estado global de sesión ────────────────────────────────────────
|
|
16
18
|
const watcher = new FileWatcher();
|
|
@@ -33,84 +35,118 @@ async function requireAI() {
|
|
|
33
35
|
}
|
|
34
36
|
return config;
|
|
35
37
|
}
|
|
38
|
+
function openBrowser(url) {
|
|
39
|
+
const cmd = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
|
|
40
|
+
exec(`${cmd} "${url}"`);
|
|
41
|
+
}
|
|
36
42
|
async function promptAIConfig() {
|
|
37
|
-
|
|
43
|
+
// 1. Auto-detect — zero config if possible
|
|
44
|
+
const detected = AIManager.autoDetect();
|
|
45
|
+
if (detected) {
|
|
46
|
+
const info = PROVIDER_INFO[detected.provider];
|
|
47
|
+
showSuccess(`${info.name} detectado (${detected.source})`);
|
|
48
|
+
const testSpinner = ora({ text: 'Verificando conexion...', color: 'cyan', spinner: 'dots' }).start();
|
|
49
|
+
const defaultModel = info.models[0].value;
|
|
50
|
+
const config = {
|
|
51
|
+
provider: detected.provider,
|
|
52
|
+
apiKey: detected.apiKey,
|
|
53
|
+
model: defaultModel,
|
|
54
|
+
authType: detected.authType,
|
|
55
|
+
};
|
|
56
|
+
const testResult = await AIManager.testConnection(config);
|
|
57
|
+
if (testResult.ok) {
|
|
58
|
+
testSpinner.succeed(c.success(`Conectado a ${info.name}`));
|
|
59
|
+
const { model } = await inquirer.prompt([{
|
|
60
|
+
type: 'select',
|
|
61
|
+
name: 'model',
|
|
62
|
+
message: 'Modelo:',
|
|
63
|
+
choices: info.models,
|
|
64
|
+
}]);
|
|
65
|
+
config.model = model;
|
|
66
|
+
return config;
|
|
67
|
+
}
|
|
68
|
+
testSpinner.fail(c.error('Sesion expirada'));
|
|
69
|
+
if (detected.authType === 'oauth') {
|
|
70
|
+
showWarning('El token de Gemini CLI expiro.');
|
|
71
|
+
showInfo('Ejecuta "gemini" en otra terminal para refrescar tu sesion.');
|
|
72
|
+
showInfo('Luego vuelve a correr "ccode init".');
|
|
73
|
+
console.log('');
|
|
74
|
+
const { continueManual } = await inquirer.prompt([{
|
|
75
|
+
type: 'confirm', name: 'continueManual',
|
|
76
|
+
message: 'Configurar manualmente mientras tanto?', default: true,
|
|
77
|
+
}]);
|
|
78
|
+
if (!continueManual) {
|
|
79
|
+
throw new Error('Ejecuta "gemini" para refrescar tu sesion y vuelve a intentar.');
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
showError(testResult.error || 'No se pudo conectar.');
|
|
84
|
+
}
|
|
85
|
+
console.log('');
|
|
86
|
+
}
|
|
87
|
+
// 2. Nothing detected — guide setup
|
|
88
|
+
showInfo('No se detecto ningun proveedor de IA.');
|
|
89
|
+
console.log('');
|
|
90
|
+
console.log(c.white(' Opciones para configurar:'));
|
|
91
|
+
console.log(c.accent(' 1. Gemini CLI') + c.dim(' — Instala: npm i -g @anthropic-ai/gemini-cli'));
|
|
92
|
+
console.log(c.dim(' Luego ejecuta "gemini" una vez para autenticarte con Google.'));
|
|
93
|
+
console.log(c.accent(' 2. Variable de entorno') + c.dim(' — export GOOGLE_API_KEY="tu-key"'));
|
|
94
|
+
console.log(c.accent(' 3. Variable de entorno') + c.dim(' — export ANTHROPIC_API_KEY="tu-key"'));
|
|
95
|
+
console.log('');
|
|
96
|
+
const { method } = await inquirer.prompt([{
|
|
38
97
|
type: 'select',
|
|
39
|
-
name: '
|
|
40
|
-
message: '
|
|
98
|
+
name: 'method',
|
|
99
|
+
message: 'Como quieres configurar?',
|
|
41
100
|
choices: [
|
|
42
|
-
{ name: '
|
|
43
|
-
{ name: '
|
|
44
|
-
{ name: '
|
|
45
|
-
{ name: '
|
|
46
|
-
{ name: ' Groq (ultra-rápido)', value: 'groq' },
|
|
47
|
-
{ name: ' Ollama (local, sin API key)', value: 'ollama' },
|
|
101
|
+
{ name: ' Tengo una API Key de Gemini (Google)', value: 'gemini-key' },
|
|
102
|
+
{ name: ' Tengo una API Key de Claude (Anthropic)', value: 'claude-key' },
|
|
103
|
+
{ name: ' Obtener API Key de Gemini gratis (abre navegador)', value: 'gemini-browser' },
|
|
104
|
+
{ name: ' Obtener API Key de Claude (abre navegador)', value: 'claude-browser' },
|
|
48
105
|
],
|
|
49
106
|
}]);
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
openai: [
|
|
59
|
-
{ name: 'GPT-4o (recomendado)', value: 'gpt-4o' },
|
|
60
|
-
{ name: 'GPT-4o mini (rápido)', value: 'gpt-4o-mini' },
|
|
61
|
-
{ name: 'GPT-4.1 (último)', value: 'gpt-4.1' },
|
|
62
|
-
{ name: 'o3-mini (razonamiento)', value: 'o3-mini' },
|
|
63
|
-
],
|
|
64
|
-
gemini: [
|
|
65
|
-
{ name: 'Gemini 2.5 Flash (recomendado)', value: 'gemini-2.5-flash' },
|
|
66
|
-
{ name: 'Gemini 2.5 Pro (máxima calidad)', value: 'gemini-2.5-pro' },
|
|
67
|
-
{ name: 'Gemini 2.0 Flash (rápido)', value: 'gemini-2.0-flash' },
|
|
68
|
-
],
|
|
69
|
-
deepseek: [
|
|
70
|
-
{ name: 'DeepSeek Chat (recomendado)', value: 'deepseek-chat' },
|
|
71
|
-
{ name: 'DeepSeek Reasoner', value: 'deepseek-reasoner' },
|
|
72
|
-
],
|
|
73
|
-
groq: [
|
|
74
|
-
{ name: 'Llama 3.3 70B (recomendado)', value: 'llama-3.3-70b-versatile' },
|
|
75
|
-
{ name: 'Llama 3.1 8B (rápido)', value: 'llama-3.1-8b-instant' },
|
|
76
|
-
{ name: 'Mixtral 8x7B', value: 'mixtral-8x7b-32768' },
|
|
77
|
-
],
|
|
78
|
-
};
|
|
79
|
-
// API Key (todos excepto Ollama)
|
|
80
|
-
if (provider !== 'ollama') {
|
|
81
|
-
const providerNames = {
|
|
82
|
-
claude: 'Anthropic', openai: 'OpenAI', gemini: 'Google AI',
|
|
83
|
-
deepseek: 'DeepSeek', groq: 'Groq',
|
|
84
|
-
};
|
|
85
|
-
const { apiKey } = await inquirer.prompt([{
|
|
86
|
-
type: 'password',
|
|
87
|
-
name: 'apiKey',
|
|
88
|
-
message: `API Key de ${providerNames[provider]}:`,
|
|
89
|
-
mask: '*',
|
|
90
|
-
validate: (v) => v.length > 10 || 'Ingresa una API Key válida',
|
|
91
|
-
}]);
|
|
92
|
-
config.apiKey = apiKey;
|
|
93
|
-
}
|
|
94
|
-
// Selección de modelo
|
|
95
|
-
if (provider === 'ollama') {
|
|
96
|
-
const { model } = await inquirer.prompt([{
|
|
97
|
-
type: 'input',
|
|
98
|
-
name: 'model',
|
|
99
|
-
message: 'Modelo de Ollama:',
|
|
100
|
-
default: 'llama3',
|
|
101
|
-
}]);
|
|
102
|
-
config.model = model;
|
|
107
|
+
const isGemini = method.startsWith('gemini');
|
|
108
|
+
const provider = isGemini ? 'gemini' : 'claude';
|
|
109
|
+
const info = PROVIDER_INFO[provider];
|
|
110
|
+
// Open browser if needed
|
|
111
|
+
if (method.endsWith('-browser')) {
|
|
112
|
+
openBrowser(info.keyUrl);
|
|
113
|
+
showInfo('Se abrio el navegador. Copia tu API Key y pegala aqui.');
|
|
114
|
+
console.log('');
|
|
103
115
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
116
|
+
const { apiKey } = await inquirer.prompt([{
|
|
117
|
+
type: 'password',
|
|
118
|
+
name: 'apiKey',
|
|
119
|
+
message: `API Key de ${info.name}:`,
|
|
120
|
+
mask: '*',
|
|
121
|
+
validate: (v) => v.length > 10 || 'API Key muy corta',
|
|
122
|
+
}]);
|
|
123
|
+
const { model } = await inquirer.prompt([{
|
|
124
|
+
type: 'select',
|
|
125
|
+
name: 'model',
|
|
126
|
+
message: 'Modelo:',
|
|
127
|
+
choices: info.models,
|
|
128
|
+
}]);
|
|
129
|
+
const config = { provider, apiKey, model, authType: 'api-key' };
|
|
130
|
+
const testSpinner = ora({ text: 'Verificando conexion...', color: 'cyan', spinner: 'dots' }).start();
|
|
131
|
+
const result = await AIManager.testConnection(config);
|
|
132
|
+
if (result.ok) {
|
|
133
|
+
testSpinner.succeed(c.success('Conexion verificada'));
|
|
134
|
+
const envVar = info.envVars[0];
|
|
135
|
+
showInfo(`Tip: Para que sea automatico la proxima vez:`);
|
|
136
|
+
console.log(c.accent(` export ${envVar}="tu-api-key"`));
|
|
137
|
+
console.log(c.dim(' Agrega esa linea a tu ~/.zshrc o ~/.bashrc'));
|
|
138
|
+
console.log('');
|
|
139
|
+
return config;
|
|
112
140
|
}
|
|
113
|
-
|
|
141
|
+
testSpinner.fail(c.error('Error de conexion'));
|
|
142
|
+
showError(result.error || 'No se pudo conectar.');
|
|
143
|
+
const { retry } = await inquirer.prompt([{
|
|
144
|
+
type: 'confirm', name: 'retry',
|
|
145
|
+
message: 'Intentar de nuevo?', default: true,
|
|
146
|
+
}]);
|
|
147
|
+
if (retry)
|
|
148
|
+
return promptAIConfig();
|
|
149
|
+
throw new Error('Configuracion de IA cancelada.');
|
|
114
150
|
}
|
|
115
151
|
function listProjectFiles(dir, prefix = '') {
|
|
116
152
|
const results = [];
|
|
@@ -180,7 +216,7 @@ async function startSession() {
|
|
|
180
216
|
if (!hasConfig) {
|
|
181
217
|
choices.push({ name: ' 🔌 Conectar proveedor de IA', value: 'connect' });
|
|
182
218
|
}
|
|
183
|
-
choices.push({ name: ' 📋 Generar / actualizar plan de tareas', value: 'plan' }, { name: ` 📊 Ver estado completo`, value: 'status' }, { name: ' 📄 Ver contexto generado', value: 'context' }, { name: ' 🔄 Actualizar contexto (re-analizar proyecto)', value: 'update' }, { name: ' 📤 Exportar contexto para otra IA', value: 'export' }, { name: ' 💡 Explicar proyecto (resumen rápido)', value: 'explain' }, { name: ' 🩺 Doctor (diagnóstico de salud)', value: 'doctor' });
|
|
219
|
+
choices.push({ name: ' 📋 Generar / actualizar plan de tareas', value: 'plan' }, { name: ` 📊 Ver estado completo`, value: 'status' }, { name: ' 📄 Ver contexto generado', value: 'context' }, { name: ' 🔄 Actualizar contexto (re-analizar proyecto)', value: 'update' }, { name: ' 🔗 Sincronizar contexto (AGENTS.md, CLAUDE.md, ...)', value: 'sync' }, { name: ' 📤 Exportar contexto para otra IA', value: 'export' }, { name: ' 💡 Explicar proyecto (resumen rápido)', value: 'explain' }, { name: ' 🩺 Doctor (diagnóstico de salud)', value: 'doctor' });
|
|
184
220
|
if (hasConfig) {
|
|
185
221
|
choices.push({ name: ' 🔌 Reconfigurar IA', value: 'connect' });
|
|
186
222
|
}
|
|
@@ -213,6 +249,7 @@ async function startSession() {
|
|
|
213
249
|
status: handleStatus,
|
|
214
250
|
context: handleContext,
|
|
215
251
|
update: handleUpdate,
|
|
252
|
+
sync: handleSync,
|
|
216
253
|
export: handleExport,
|
|
217
254
|
explain: handleExplain,
|
|
218
255
|
doctor: handleDoctor,
|
|
@@ -342,6 +379,19 @@ async function handleInit() {
|
|
|
342
379
|
{ name: 'config.json ', desc: `IA: ${aiConfig.provider}` },
|
|
343
380
|
]);
|
|
344
381
|
console.log('');
|
|
382
|
+
// ─── Sincronizar contexto a todas las herramientas AI ───
|
|
383
|
+
const syncSpinner = ora({ text: 'Sincronizando contexto con herramientas AI...', color: 'cyan', spinner: 'dots' }).start();
|
|
384
|
+
try {
|
|
385
|
+
const exporter = new ContextExporter();
|
|
386
|
+
const exported = await exporter.exportAll();
|
|
387
|
+
syncSpinner.succeed(c.success('Contexto sincronizado'));
|
|
388
|
+
console.log(c.dim('\n Archivos de contexto generados:\n'));
|
|
389
|
+
showFileTree(exported.map(e => ({ name: e.file, desc: e.label.split('(')[1]?.replace(')', '') || '' })));
|
|
390
|
+
console.log('');
|
|
391
|
+
}
|
|
392
|
+
catch {
|
|
393
|
+
syncSpinner.warn(c.warning('No se pudo sincronizar (los archivos .ccode/ se crearon correctamente)'));
|
|
394
|
+
}
|
|
345
395
|
showSuccess('CCODE se queda activo observando tu proyecto.');
|
|
346
396
|
showInfo('Abre otra terminal y empieza a desarrollar.');
|
|
347
397
|
showInfo('CCODE detectará los cambios automáticamente.');
|
|
@@ -385,17 +435,9 @@ async function handleConnect() {
|
|
|
385
435
|
return;
|
|
386
436
|
}
|
|
387
437
|
const config = await promptAIConfig();
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
spinner.succeed(c.success('Conexión verificada'));
|
|
392
|
-
await AIManager.saveConfig(config);
|
|
393
|
-
showSuccess('Configuración guardada.');
|
|
394
|
-
}
|
|
395
|
-
else {
|
|
396
|
-
spinner.fail(c.error('No se pudo conectar'));
|
|
397
|
-
showError('Verifica tu API Key, conexión a internet, o que Ollama esté corriendo.');
|
|
398
|
-
}
|
|
438
|
+
// promptAIConfig already verifies connection, just save
|
|
439
|
+
await AIManager.saveConfig(config);
|
|
440
|
+
showSuccess('Configuracion guardada.');
|
|
399
441
|
}
|
|
400
442
|
// ─── PLAN ───────────────────────────────────────────────────────────
|
|
401
443
|
async function handlePlan() {
|
|
@@ -781,6 +823,50 @@ Responde ÚNICAMENTE con JSON válido:
|
|
|
781
823
|
console.log(c.accent(' Cambios detectados:'));
|
|
782
824
|
result.changes.forEach(change => console.log(c.dim(` • ${change}`)));
|
|
783
825
|
}
|
|
826
|
+
// Sincronizar exports después de actualizar contexto
|
|
827
|
+
const syncSpinner = ora({ text: 'Sincronizando exports...', color: 'cyan', spinner: 'dots' }).start();
|
|
828
|
+
try {
|
|
829
|
+
const exporter = new ContextExporter();
|
|
830
|
+
const existing = await exporter.detectExisting();
|
|
831
|
+
if (existing.length > 0) {
|
|
832
|
+
for (const format of existing) {
|
|
833
|
+
await exporter.exportFormat(format);
|
|
834
|
+
}
|
|
835
|
+
syncSpinner.succeed(c.success(`${existing.length} export${existing.length > 1 ? 's' : ''} actualizado${existing.length > 1 ? 's' : ''}`));
|
|
836
|
+
}
|
|
837
|
+
else {
|
|
838
|
+
syncSpinner.info(c.dim('Sin exports previos. Usa "export" para generarlos.'));
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
catch {
|
|
842
|
+
syncSpinner.warn(c.warning('No se pudieron actualizar los exports'));
|
|
843
|
+
}
|
|
844
|
+
console.log('');
|
|
845
|
+
}
|
|
846
|
+
catch (error) {
|
|
847
|
+
spinner.fail('Error');
|
|
848
|
+
showError(error instanceof Error ? error.message : String(error));
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
// ─── SYNC ──────────────────────────────────────────────────────────
|
|
852
|
+
async function handleSync() {
|
|
853
|
+
if (!(await requireInit()))
|
|
854
|
+
return;
|
|
855
|
+
showHeader('Sincronizar Contexto', 'Genera archivos de contexto para cada herramienta AI');
|
|
856
|
+
const exporter = new ContextExporter();
|
|
857
|
+
const existing = await exporter.detectExisting();
|
|
858
|
+
if (existing.length > 0) {
|
|
859
|
+
showInfo(`Exports existentes: ${existing.map(f => ContextExporter.FORMATS[f].file).join(', ')}`);
|
|
860
|
+
}
|
|
861
|
+
const spinner = ora({ text: 'Generando archivos de contexto...', color: 'cyan', spinner: 'dots' }).start();
|
|
862
|
+
try {
|
|
863
|
+
const exported = await exporter.exportAll();
|
|
864
|
+
spinner.succeed(c.success('Contexto sincronizado con todas las herramientas'));
|
|
865
|
+
console.log('');
|
|
866
|
+
showFileTree(exported.map(e => ({ name: e.file, desc: e.label.split('(')[1]?.replace(')', '') || '' })));
|
|
867
|
+
console.log('');
|
|
868
|
+
showInfo('Cada herramienta AI leera su archivo automaticamente.');
|
|
869
|
+
showInfo('Los archivos se actualizan con cada "sync", "update" o "init".');
|
|
784
870
|
console.log('');
|
|
785
871
|
}
|
|
786
872
|
catch (error) {
|
|
@@ -793,12 +879,61 @@ async function handleExport() {
|
|
|
793
879
|
if (!(await requireInit()))
|
|
794
880
|
return;
|
|
795
881
|
showHeader('Exportar Contexto');
|
|
796
|
-
const
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
882
|
+
const { mode } = await inquirer.prompt([{
|
|
883
|
+
type: 'select',
|
|
884
|
+
name: 'mode',
|
|
885
|
+
message: 'Tipo de export:',
|
|
886
|
+
choices: [
|
|
887
|
+
{ name: ' Sincronizar con todas las herramientas AI', value: 'all' },
|
|
888
|
+
{ name: ' Solo export universal (.md para copiar/pegar)', value: 'universal' },
|
|
889
|
+
{ name: ' Elegir herramientas específicas', value: 'pick' },
|
|
890
|
+
],
|
|
891
|
+
}]);
|
|
892
|
+
const exporter = new ContextExporter();
|
|
893
|
+
if (mode === 'universal') {
|
|
894
|
+
const content = await exporter.generateUniversalExport();
|
|
895
|
+
const exportPath = path.join(process.cwd(), '.ccode', 'context-export.md');
|
|
896
|
+
await FileUtils.writeFile(exportPath, content);
|
|
897
|
+
showSuccess('Contexto exportado a .ccode/context-export.md');
|
|
898
|
+
showInfo('Copia el contenido y pegalo en cualquier chat de IA.');
|
|
899
|
+
console.log('');
|
|
900
|
+
return;
|
|
901
|
+
}
|
|
902
|
+
if (mode === 'pick') {
|
|
903
|
+
const formats = Object.entries(ContextExporter.FORMATS);
|
|
904
|
+
const existing = await exporter.detectExisting();
|
|
905
|
+
const { selected } = await inquirer.prompt([{
|
|
906
|
+
type: 'checkbox',
|
|
907
|
+
name: 'selected',
|
|
908
|
+
message: 'Selecciona los formatos:',
|
|
909
|
+
choices: formats.map(([key, info]) => ({
|
|
910
|
+
name: ` ${info.label}${existing.includes(key) ? c.dim(' (existe)') : ''}`,
|
|
911
|
+
value: key,
|
|
912
|
+
checked: true,
|
|
913
|
+
})),
|
|
914
|
+
}]);
|
|
915
|
+
if (selected.length === 0) {
|
|
916
|
+
showWarning('No se selecciono ningun formato.');
|
|
917
|
+
return;
|
|
918
|
+
}
|
|
919
|
+
const spinner = ora({ text: 'Generando exports...', color: 'cyan', spinner: 'dots' }).start();
|
|
920
|
+
for (const format of selected) {
|
|
921
|
+
await exporter.exportFormat(format);
|
|
922
|
+
}
|
|
923
|
+
spinner.succeed(c.success(`${selected.length} formato${selected.length > 1 ? 's' : ''} exportado${selected.length > 1 ? 's' : ''}`));
|
|
924
|
+
for (const format of selected) {
|
|
925
|
+
const info = ContextExporter.FORMATS[format];
|
|
926
|
+
console.log(c.dim(` ${info.file}`));
|
|
927
|
+
}
|
|
928
|
+
console.log('');
|
|
929
|
+
return;
|
|
930
|
+
}
|
|
931
|
+
// mode === 'all'
|
|
932
|
+
const spinner = ora({ text: 'Sincronizando con todas las herramientas AI...', color: 'cyan', spinner: 'dots' }).start();
|
|
933
|
+
const exported = await exporter.exportAll();
|
|
934
|
+
spinner.succeed(c.success('Contexto sincronizado'));
|
|
935
|
+
console.log(c.dim('\n Archivos generados:\n'));
|
|
936
|
+
showFileTree(exported.map(e => ({ name: e.file, desc: e.label.split('(')[1]?.replace(')', '') || '' })));
|
|
802
937
|
console.log('');
|
|
803
938
|
}
|
|
804
939
|
// ─── EXPLAIN ────────────────────────────────────────────────────────
|
|
@@ -878,14 +1013,15 @@ async function handleDoctor() {
|
|
|
878
1013
|
const config = await AIManager.loadConfig();
|
|
879
1014
|
if (config) {
|
|
880
1015
|
console.log(c.success(` ✓ Configurado: ${config.provider} (${config.model || 'default'})`));
|
|
881
|
-
// Test de
|
|
882
|
-
const spinner = ora({ text: ' Probando
|
|
883
|
-
const
|
|
884
|
-
if (
|
|
885
|
-
spinner.succeed(c.success('
|
|
1016
|
+
// Test de conexion
|
|
1017
|
+
const spinner = ora({ text: ' Probando conexion...', color: 'cyan', spinner: 'dots' }).start();
|
|
1018
|
+
const connResult = await AIManager.testConnection(config);
|
|
1019
|
+
if (connResult.ok) {
|
|
1020
|
+
spinner.succeed(c.success('Conexion activa'));
|
|
886
1021
|
}
|
|
887
1022
|
else {
|
|
888
1023
|
spinner.fail(c.error('No se pudo conectar'));
|
|
1024
|
+
showError(` ${connResult.error}`);
|
|
889
1025
|
issues++;
|
|
890
1026
|
}
|
|
891
1027
|
}
|
|
@@ -916,7 +1052,26 @@ async function handleDoctor() {
|
|
|
916
1052
|
issues++;
|
|
917
1053
|
}
|
|
918
1054
|
}
|
|
919
|
-
// 4.
|
|
1055
|
+
// 4. Exports de contexto
|
|
1056
|
+
console.log('');
|
|
1057
|
+
console.log(c.bold(' Exports de contexto'));
|
|
1058
|
+
const exporter = new ContextExporter();
|
|
1059
|
+
const existingExports = await exporter.detectExisting();
|
|
1060
|
+
if (existingExports.length > 0) {
|
|
1061
|
+
for (const format of existingExports) {
|
|
1062
|
+
const info = ContextExporter.FORMATS[format];
|
|
1063
|
+
console.log(c.success(` ✓ ${info.file}`));
|
|
1064
|
+
}
|
|
1065
|
+
const missing = Object.keys(ContextExporter.FORMATS).length - existingExports.length;
|
|
1066
|
+
if (missing > 0) {
|
|
1067
|
+
console.log(c.dim(` ${missing} formato${missing > 1 ? 's' : ''} disponible${missing > 1 ? 's' : ''} sin generar`));
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
else {
|
|
1071
|
+
console.log(c.warning(' ⚠ Sin exports — ejecuta "Sincronizar contexto" para generarlos'));
|
|
1072
|
+
issues++;
|
|
1073
|
+
}
|
|
1074
|
+
// 5. Archivos del proyecto
|
|
920
1075
|
console.log('');
|
|
921
1076
|
console.log(c.bold(' Proyecto'));
|
|
922
1077
|
const projectFiles = listProjectFiles(process.cwd());
|
|
@@ -959,6 +1114,7 @@ async function main() {
|
|
|
959
1114
|
program.command('status').description('Estado del proyecto').action(handleStatus);
|
|
960
1115
|
program.command('context').description('Ver contexto generado').action(handleContext);
|
|
961
1116
|
program.command('update').description('Re-analiza y actualiza el contexto').action(handleUpdate);
|
|
1117
|
+
program.command('sync').description('Sincroniza contexto con todas las herramientas AI').action(handleSync);
|
|
962
1118
|
program.command('export').description('Exporta contexto como .md para cualquier IA').action(handleExport);
|
|
963
1119
|
program.command('explain').description('Resumen rápido del proyecto').action(handleExplain);
|
|
964
1120
|
program.command('doctor').description('Diagnóstico de salud del proyecto').action(handleDoctor);
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ContextExporter: genera archivos de contexto para cada herramienta de IA.
|
|
3
|
+
*
|
|
4
|
+
* Lee la fuente de verdad (.ccode/) y produce exports específicos:
|
|
5
|
+
* - AGENTS.md → Estándar abierto (Linux Foundation)
|
|
6
|
+
* - CLAUDE.md → Claude Code
|
|
7
|
+
* - GEMINI.md → Gemini CLI
|
|
8
|
+
* - .cursorrules → Cursor
|
|
9
|
+
* - copilot-instructions.md → GitHub Copilot
|
|
10
|
+
*
|
|
11
|
+
* Cada export se adapta al formato y expectativas de la herramienta destino.
|
|
12
|
+
*/
|
|
13
|
+
export declare class ContextExporter {
|
|
14
|
+
private readonly ccodePath;
|
|
15
|
+
private readonly projectRoot;
|
|
16
|
+
static readonly FORMATS: Record<string, {
|
|
17
|
+
file: string;
|
|
18
|
+
label: string;
|
|
19
|
+
}>;
|
|
20
|
+
constructor(projectRoot?: string);
|
|
21
|
+
/**
|
|
22
|
+
* Lee todos los archivos de contexto de .ccode/
|
|
23
|
+
*/
|
|
24
|
+
private readSources;
|
|
25
|
+
/**
|
|
26
|
+
* Formatea las tareas como lista legible.
|
|
27
|
+
*/
|
|
28
|
+
private formatTasks;
|
|
29
|
+
/**
|
|
30
|
+
* Genera AGENTS.md — Estándar abierto compatible con múltiples herramientas.
|
|
31
|
+
* Sigue la convención de AGENTS.md (Linux Foundation / AAIF).
|
|
32
|
+
*/
|
|
33
|
+
generateAgentsMd(): Promise<string>;
|
|
34
|
+
/**
|
|
35
|
+
* Genera CLAUDE.md — Optimizado para Claude Code.
|
|
36
|
+
* Claude Code lee este archivo automáticamente del root del proyecto.
|
|
37
|
+
*/
|
|
38
|
+
generateClaudeMd(): Promise<string>;
|
|
39
|
+
/**
|
|
40
|
+
* Genera GEMINI.md — Para Gemini CLI.
|
|
41
|
+
*/
|
|
42
|
+
generateGeminiMd(): Promise<string>;
|
|
43
|
+
/**
|
|
44
|
+
* Genera .cursorrules — Instrucciones para Cursor IDE.
|
|
45
|
+
* Cursor lee este archivo del root del proyecto automáticamente.
|
|
46
|
+
*/
|
|
47
|
+
generateCursorRules(): Promise<string>;
|
|
48
|
+
/**
|
|
49
|
+
* Genera copilot-instructions.md — Para GitHub Copilot.
|
|
50
|
+
* Se ubica en .github/copilot-instructions.md
|
|
51
|
+
*/
|
|
52
|
+
generateCopilotInstructions(): Promise<string>;
|
|
53
|
+
/**
|
|
54
|
+
* Genera el export universal (.ccode/context-export.md).
|
|
55
|
+
*/
|
|
56
|
+
generateUniversalExport(): Promise<string>;
|
|
57
|
+
/**
|
|
58
|
+
* Genera y escribe un formato específico.
|
|
59
|
+
*/
|
|
60
|
+
exportFormat(format: string): Promise<string>;
|
|
61
|
+
/**
|
|
62
|
+
* Exporta todos los formatos de una vez.
|
|
63
|
+
* Retorna la lista de archivos generados.
|
|
64
|
+
*/
|
|
65
|
+
exportAll(): Promise<Array<{
|
|
66
|
+
format: string;
|
|
67
|
+
file: string;
|
|
68
|
+
label: string;
|
|
69
|
+
}>>;
|
|
70
|
+
/**
|
|
71
|
+
* Verifica qué formatos ya existen en el proyecto.
|
|
72
|
+
*/
|
|
73
|
+
detectExisting(): Promise<string[]>;
|
|
74
|
+
}
|