@cocaxcode/ai-context-inspector 0.3.2 → 0.4.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/dist/index.js CHANGED
@@ -1,13 +1,356 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ ACI_BUNDLE,
4
+ ACI_DIR,
5
+ detectEnvVars,
6
+ detectTargetTools,
7
+ executeImport,
8
+ exportEcosystem,
3
9
  generateHtml,
10
+ isSensitiveVar,
11
+ loadBundle,
12
+ planImport,
4
13
  runAllScanners
5
- } from "./chunk-G57UDS2C.js";
14
+ } from "./chunk-3NLUFQSB.js";
6
15
 
7
16
  // src/cli.ts
8
17
  import { parseArgs } from "util";
9
18
  import { access, mkdir, writeFile } from "fs/promises";
10
- import { dirname, resolve } from "path";
19
+ import { dirname, join, resolve } from "path";
20
+
21
+ // src/ecosystem/prompts.ts
22
+ import * as readline from "readline/promises";
23
+ import { stdin, stdout } from "process";
24
+ var CATEGORY_LABELS = {
25
+ mcp: "MCP Servers",
26
+ skills: "Skills",
27
+ agents: "Agents",
28
+ memories: "Memorias",
29
+ context: "Context Files"
30
+ };
31
+ var ALL_TARGETS = [
32
+ "claude",
33
+ "cursor",
34
+ "windsurf",
35
+ "copilot",
36
+ "gemini",
37
+ "codex",
38
+ "opencode"
39
+ ];
40
+ function createPromptInterface() {
41
+ return readline.createInterface({ input: stdin, output: stdout });
42
+ }
43
+ function maskValue(value) {
44
+ if (value.length <= 10) return "****";
45
+ return `${value.slice(0, 6)}...${value.slice(-4)}`;
46
+ }
47
+ function parseYesNo(input) {
48
+ const trimmed = input.trim().toLowerCase();
49
+ if (["s", "si", "s\xED", "y", "yes"].includes(trimmed)) return true;
50
+ if (["n", "no"].includes(trimmed)) return false;
51
+ if (trimmed === "") return void 0;
52
+ return void 0;
53
+ }
54
+ async function askNumber(rl, prompt, min, max) {
55
+ while (true) {
56
+ const answer = await rl.question(prompt);
57
+ const num = parseInt(answer.trim(), 10);
58
+ if (!isNaN(num) && num >= min && num <= max) return num;
59
+ console.error(` Entrada inv\xE1lida. Ingresa un n\xFAmero entre ${min} y ${max}.`);
60
+ }
61
+ }
62
+ function parseNumberList(input, min, max) {
63
+ const trimmed = input.trim();
64
+ if (trimmed === "") return [];
65
+ const parts = trimmed.split(",").map((s) => s.trim());
66
+ const nums = [];
67
+ for (const part of parts) {
68
+ const num = parseInt(part, 10);
69
+ if (isNaN(num) || num < min || num > max) return null;
70
+ nums.push(num);
71
+ }
72
+ return nums;
73
+ }
74
+ async function promptCategories(rl, available, verb) {
75
+ console.error(`
76
+ Categor\xEDas disponibles para ${verb}:`);
77
+ for (let i = 0; i < available.length; i++) {
78
+ const { category, count } = available[i];
79
+ console.error(` [${i + 1}] ${CATEGORY_LABELS[category]} (${count})`);
80
+ }
81
+ while (true) {
82
+ const answer = await rl.question(
83
+ "\nIngresa n\xFAmeros separados por coma, o Enter para todos: "
84
+ );
85
+ const nums = parseNumberList(answer, 1, available.length);
86
+ if (nums === null) {
87
+ console.error(" Entrada inv\xE1lida. Usa n\xFAmeros separados por coma.");
88
+ continue;
89
+ }
90
+ if (nums.length === 0) {
91
+ return available.map((a) => a.category);
92
+ }
93
+ return nums.map((n) => available[n - 1].category);
94
+ }
95
+ }
96
+ async function promptExportSecrets(rl, detectedVars) {
97
+ if (detectedVars.length === 0) {
98
+ return { mode: "none" };
99
+ }
100
+ const serverNames = new Set(detectedVars.map((v) => v.serverName));
101
+ console.error(
102
+ `
103
+ Se detectaron ${detectedVars.length} variables de entorno en ${serverNames.size} MCP server${serverNames.size > 1 ? "s" : ""}:
104
+ `
105
+ );
106
+ const maxVarLen = Math.max(...detectedVars.map((v) => v.varName.length));
107
+ const maxServerLen = Math.max(...detectedVars.map((v) => v.serverName.length));
108
+ for (const v of detectedVars) {
109
+ const varPad = v.varName.padEnd(maxVarLen);
110
+ const serverPad = v.serverName.padEnd(maxServerLen);
111
+ const label = v.isSensitive ? "\u{1F510} sensible" : " no sensible";
112
+ console.error(` ${varPad} (${serverPad}) ${label}`);
113
+ }
114
+ console.error("\n\xBFC\xF3mo manejar los secretos?");
115
+ console.error(" [1] Redactar todos (reemplazar con ${VAR_NAME})");
116
+ console.error(
117
+ " [2] Incluir todos (\u26A0\uFE0F valores reales en texto plano)"
118
+ );
119
+ console.error(" [3] Decidir uno por uno");
120
+ const option = await askNumber(rl, "\nOpci\xF3n: ", 1, 3);
121
+ if (option === 1) return { mode: "none" };
122
+ if (option === 2) return { mode: "all" };
123
+ const decisions = await promptSecretDecisionsOneByOne(rl, detectedVars);
124
+ return { mode: "custom", decisions };
125
+ }
126
+ async function promptSecretDecisionsOneByOne(rl, detectedVars) {
127
+ console.error("\nDecide por cada variable (S=incluir, N=redactar):\n");
128
+ const decisions = {};
129
+ for (const v of detectedVars) {
130
+ const defaultInclude = !v.isSensitive;
131
+ const defaultLabel = defaultInclude ? "S" : "N";
132
+ const sensitiveTag = v.isSensitive ? " \u{1F510}" : "";
133
+ while (true) {
134
+ const answer = await rl.question(
135
+ ` ${v.varName} (${v.serverName})${sensitiveTag} [${defaultLabel}]: `
136
+ );
137
+ const trimmed = answer.trim();
138
+ if (trimmed === "") {
139
+ decisions[v.varName] = defaultInclude;
140
+ break;
141
+ }
142
+ const parsed = parseYesNo(trimmed);
143
+ if (parsed !== void 0) {
144
+ decisions[v.varName] = parsed;
145
+ break;
146
+ }
147
+ console.error(" Entrada inv\xE1lida. Usa S (incluir) o N (redactar).");
148
+ }
149
+ }
150
+ return decisions;
151
+ }
152
+ async function promptTargetTool(rl, detected) {
153
+ if (detected.length === 1) {
154
+ return detected[0];
155
+ }
156
+ if (detected.length > 1) {
157
+ console.error("\nSe detectaron m\xFAltiples herramientas AI:");
158
+ for (let i = 0; i < detected.length; i++) {
159
+ console.error(` [${i + 1}] ${detected[i]}`);
160
+ }
161
+ const option2 = await askNumber(
162
+ rl,
163
+ "\xBFCu\xE1l usar como destino? ",
164
+ 1,
165
+ detected.length
166
+ );
167
+ return detected[option2 - 1];
168
+ }
169
+ console.error("\nNo se detect\xF3 ninguna herramienta AI configurada.");
170
+ console.error("\xBFCu\xE1l configurar?");
171
+ for (let i = 0; i < ALL_TARGETS.length; i++) {
172
+ console.error(` [${i + 1}] ${ALL_TARGETS[i]}`);
173
+ }
174
+ const option = await askNumber(rl, "Opci\xF3n: ", 1, ALL_TARGETS.length);
175
+ return ALL_TARGETS[option - 1];
176
+ }
177
+ async function promptImportSecrets(rl, bundle) {
178
+ const hasReal = bundle.envVarsIncluded.length > 0;
179
+ const hasRedacted = bundle.envVarsRedacted.length > 0;
180
+ if (!hasReal && !hasRedacted) {
181
+ return { mode: "none" };
182
+ }
183
+ if (hasReal) {
184
+ console.error(
185
+ `
186
+ El bundle contiene valores reales para ${bundle.envVarsIncluded.length} variable${bundle.envVarsIncluded.length > 1 ? "s" : ""}:`
187
+ );
188
+ console.error(` ${bundle.envVarsIncluded.join(", ")}`);
189
+ if (hasRedacted) {
190
+ console.error(
191
+ `Y ${bundle.envVarsRedacted.length} variable${bundle.envVarsRedacted.length > 1 ? "s" : ""} redactada${bundle.envVarsRedacted.length > 1 ? "s" : ""}:`
192
+ );
193
+ console.error(` ${bundle.envVarsRedacted.join(", ")}`);
194
+ }
195
+ console.error("\n\xBFC\xF3mo manejar los secretos?");
196
+ console.error(" [1] Redactar todos (dejar como ${VAR_NAME})");
197
+ console.error(" [2] Usar valores del bundle");
198
+ console.error(" [3] Decidir uno por uno");
199
+ const option2 = await askNumber(rl, "\nOpci\xF3n: ", 1, 3);
200
+ if (option2 === 1) return { mode: "none" };
201
+ if (option2 === 2) {
202
+ const values3 = {};
203
+ for (const v of bundle.envVarsIncluded) {
204
+ values3[v] = bundle.envValues[v] ?? null;
205
+ }
206
+ for (const v of bundle.envVarsRedacted) {
207
+ values3[v] = null;
208
+ }
209
+ return { mode: "all", values: values3 };
210
+ }
211
+ const values2 = {};
212
+ for (const varName of bundle.envVarsIncluded) {
213
+ const val = bundle.envValues[varName] ?? "";
214
+ const sensitive = isSensitiveVar(varName);
215
+ const displayVal = sensitive ? maskValue(val) : val;
216
+ const sensitiveTag = sensitive ? " \u{1F510}" : "";
217
+ console.error(`
218
+ ${varName} = "${displayVal}"${sensitiveTag}`);
219
+ console.error(" [1] Usar valor del bundle");
220
+ console.error(` [2] No importar (dejar \${${varName}})`);
221
+ console.error(" [3] Introducir nuevo valor");
222
+ const opt = await askNumber(rl, " Opci\xF3n [1]: ", 1, 3);
223
+ if (opt === 1) {
224
+ values2[varName] = val;
225
+ } else if (opt === 2) {
226
+ values2[varName] = null;
227
+ } else {
228
+ const newVal = await rl.question(` ${varName}: `);
229
+ values2[varName] = newVal;
230
+ }
231
+ }
232
+ for (const varName of bundle.envVarsRedacted) {
233
+ const newVal = await rl.question(` ${varName}: `);
234
+ values2[varName] = newVal.trim() === "" ? null : newVal;
235
+ }
236
+ return { mode: "custom", values: values2 };
237
+ }
238
+ console.error(
239
+ `
240
+ El bundle contiene ${bundle.envVarsRedacted.length} variable${bundle.envVarsRedacted.length > 1 ? "s" : ""} redactada${bundle.envVarsRedacted.length > 1 ? "s" : ""}:`
241
+ );
242
+ console.error(` ${bundle.envVarsRedacted.join(", ")}`);
243
+ console.error("\n [1] Dejar como ${VAR_NAME} (configurar despu\xE9s)");
244
+ console.error(" [2] Introducir valores ahora");
245
+ const option = await askNumber(rl, "\nOpci\xF3n: ", 1, 2);
246
+ if (option === 1) return { mode: "none" };
247
+ const values = {};
248
+ console.error("");
249
+ for (const varName of bundle.envVarsRedacted) {
250
+ const newVal = await rl.question(` ${varName}: `);
251
+ values[varName] = newVal.trim() === "" ? null : newVal;
252
+ }
253
+ return { mode: "custom", values };
254
+ }
255
+ async function promptConfirmPlan(rl, plan) {
256
+ console.error(`
257
+ Plan de importaci\xF3n (${plan.target}):
258
+ `);
259
+ for (const action of plan.actions) {
260
+ const categoryLabel = CATEGORY_LABELS[action.category] ?? action.category;
261
+ const shortCat = action.category === "mcp" ? "MCP" : action.category === "context" ? "Context" : categoryLabel.replace(/s$/, "");
262
+ if (action.action === "install") {
263
+ console.error(
264
+ ` \u2705 Instalar ${shortCat}: ${action.name} \u2192 ${action.targetPath}`
265
+ );
266
+ } else if (action.action === "skip") {
267
+ const reason = action.reason ? ` (${action.reason})` : "";
268
+ console.error(
269
+ ` \u23ED\uFE0F Omitir ${shortCat}: ${action.name}${reason}`
270
+ );
271
+ } else if (action.action === "overwrite") {
272
+ console.error(
273
+ ` \u26A0\uFE0F Sobrescribir ${shortCat}: ${action.name} \u2192 ${action.targetPath}`
274
+ );
275
+ } else if (action.action === "unsupported") {
276
+ const reason = action.reason ? ` (${action.reason})` : "";
277
+ console.error(
278
+ ` \u274C No soportado ${shortCat}: ${action.name}${reason}`
279
+ );
280
+ }
281
+ }
282
+ console.error(
283
+ `
284
+ Resumen: ${plan.summary.install} instalar, ${plan.summary.skip} omitir, ${plan.summary.unsupported} no soportado`
285
+ );
286
+ if (plan.pendingEnvVars.length > 0) {
287
+ console.error(
288
+ ` \u26A0\uFE0F ${plan.pendingEnvVars.length} env vars pendientes: ${plan.pendingEnvVars.join(", ")}`
289
+ );
290
+ }
291
+ while (true) {
292
+ const answer = await rl.question("\n\xBFEjecutar? [s/N]: ");
293
+ const trimmed = answer.trim();
294
+ if (trimmed === "") return false;
295
+ const parsed = parseYesNo(trimmed);
296
+ if (parsed !== void 0) return parsed;
297
+ console.error(" Entrada inv\xE1lida. Usa s (s\xED) o n (no).");
298
+ }
299
+ }
300
+ function printImportResult(result) {
301
+ console.error("\nImportaci\xF3n completada:");
302
+ console.error(` \u2705 ${result.installed.length} instalados`);
303
+ console.error(` \u23ED\uFE0F ${result.skipped.length} omitidos`);
304
+ console.error(` \u274C ${result.unsupported.length} no soportados`);
305
+ if (result.pendingEnvVars.length > 0) {
306
+ console.error("\n Variables de entorno pendientes de configuraci\xF3n:");
307
+ for (const varName of result.pendingEnvVars) {
308
+ console.error(` ${varName} \u2192 configurar manualmente`);
309
+ }
310
+ }
311
+ }
312
+ function printExportResult(bundle, redactedVars, includedVars, gitignoreAdded) {
313
+ const res = bundle.resources;
314
+ console.error("\nBundle exportado: .aci/bundle.json");
315
+ if (res.mcpServers?.length > 0)
316
+ console.error(` MCP Servers: ${res.mcpServers.length}`);
317
+ if (res.skills?.length > 0)
318
+ console.error(` Skills: ${res.skills.length}`);
319
+ if (res.agents?.length > 0)
320
+ console.error(` Agents: ${res.agents.length}`);
321
+ if (res.memories?.length > 0)
322
+ console.error(` Memorias: ${res.memories.length}`);
323
+ if (res.contextFiles?.length > 0)
324
+ console.error(` Context Files: ${res.contextFiles.length}`);
325
+ if (redactedVars.length > 0) {
326
+ console.error(` Variables redactadas: ${redactedVars.join(", ")}`);
327
+ }
328
+ if (includedVars.length > 0) {
329
+ console.error(` Variables incluidas: ${includedVars.join(", ")}`);
330
+ }
331
+ if (gitignoreAdded) {
332
+ console.error(" \u2705 .aci/ a\xF1adido al .gitignore");
333
+ }
334
+ if (bundle.warnings.length > 0) {
335
+ console.error("\n Advertencias:");
336
+ for (const w of bundle.warnings) {
337
+ console.error(` \u26A0\uFE0F ${w}`);
338
+ }
339
+ }
340
+ }
341
+ async function promptAutoImport(rl) {
342
+ console.error("\nSe detect\xF3 un bundle de ecosistema AI en .aci/");
343
+ while (true) {
344
+ const answer = await rl.question("\xBFQuieres importarlo? [s/N]: ");
345
+ const trimmed = answer.trim();
346
+ if (trimmed === "") return false;
347
+ const parsed = parseYesNo(trimmed);
348
+ if (parsed !== void 0) return parsed;
349
+ console.error(" Entrada inv\xE1lida. Usa s (s\xED) o n (no).");
350
+ }
351
+ }
352
+
353
+ // src/cli.ts
11
354
  function parseCliArgs(argv) {
12
355
  const { values } = parseArgs({
13
356
  args: argv,
@@ -38,14 +381,54 @@ function parseCliArgs(argv) {
38
381
  timeout: parseInt(values.timeout ?? "10000", 10)
39
382
  };
40
383
  }
384
+ function parseExportArgs(argv) {
385
+ const { values } = parseArgs({
386
+ args: argv,
387
+ options: {
388
+ dir: { type: "string", short: "d" },
389
+ output: { type: "string", short: "o" },
390
+ "include-user": { type: "boolean", default: false },
391
+ only: { type: "string" },
392
+ secrets: { type: "string" },
393
+ help: { type: "boolean", short: "h", default: false }
394
+ },
395
+ strict: false,
396
+ allowPositionals: true
397
+ });
398
+ return values;
399
+ }
400
+ function parseImportArgs(argv) {
401
+ const { values, positionals } = parseArgs({
402
+ args: argv,
403
+ options: {
404
+ dir: { type: "string", short: "d" },
405
+ target: { type: "string" },
406
+ scope: { type: "string" },
407
+ force: { type: "boolean", default: false },
408
+ yes: { type: "boolean", default: false },
409
+ only: { type: "string" },
410
+ secrets: { type: "string" },
411
+ help: { type: "boolean", short: "h", default: false }
412
+ },
413
+ strict: false,
414
+ allowPositionals: true
415
+ });
416
+ return { values, positionals };
417
+ }
41
418
  function printHelp() {
42
419
  console.error(`
43
420
  ai-context-inspector \u2014 Escanea el ecosistema AI de un proyecto
44
421
 
45
422
  Uso:
46
423
  npx @cocaxcode/ai-context-inspector [opciones]
424
+ npx @cocaxcode/ai-context-inspector export [opciones]
425
+ npx @cocaxcode/ai-context-inspector import [archivo] [opciones]
47
426
 
48
- Opciones:
427
+ Subcomandos:
428
+ export Exportar ecosistema AI a bundle portable (.aci/)
429
+ import [archivo] Importar bundle a otra herramienta AI
430
+
431
+ Opciones generales:
49
432
  -d, --dir <ruta> Directorio a escanear (default: cwd)
50
433
  -o, --output <ruta> Archivo HTML de salida (default: ai-context-report.html)
51
434
  --json Output JSON en stdout (no genera HTML)
@@ -54,9 +437,260 @@ function printHelp() {
54
437
  --timeout <ms> Timeout de introspecci\xF3n MCP (default: 10000)
55
438
  --mcp Arrancar como MCP server
56
439
  -h, --help Mostrar ayuda
440
+
441
+ Opciones de export:
442
+ -d, --dir <ruta> Directorio a exportar (default: cwd)
443
+ -o, --output <ruta> Directorio de salida (default: .aci/)
444
+ --include-user Incluir configuraci\xF3n del directorio de usuario
445
+ --only <categor\xEDas> Solo exportar: mcp,skills,agents,memories,context
446
+ --secrets <modo> Modo de secretos: none | all (default: interactivo)
447
+
448
+ Opciones de import:
449
+ -d, --dir <ruta> Directorio destino (default: cwd)
450
+ --target <tool> Herramienta destino: claude|cursor|windsurf|copilot|gemini|codex|opencode
451
+ --scope <scope> Scope: project | user
452
+ --force Sobreescribir recursos existentes
453
+ --yes Saltar confirmaci\xF3n
454
+ --only <categor\xEDas> Solo importar: mcp,skills,agents,memories,context
455
+ --secrets <modo> Modo de secretos: none | all (default: interactivo)
456
+ `);
457
+ }
458
+ function printExportHelp() {
459
+ console.error(`
460
+ ai-context-inspector export \u2014 Exportar ecosistema AI a bundle portable
461
+
462
+ Uso:
463
+ npx @cocaxcode/ai-context-inspector export [opciones]
464
+
465
+ Opciones:
466
+ -d, --dir <ruta> Directorio a exportar (default: cwd)
467
+ -o, --output <ruta> Directorio de salida (default: .aci/)
468
+ --include-user Incluir configuraci\xF3n del directorio de usuario
469
+ --only <categor\xEDas> Solo exportar: mcp,skills,agents,memories,context
470
+ --secrets <modo> Modo de secretos: none | all (default: interactivo)
471
+ -h, --help Mostrar ayuda
57
472
  `);
58
473
  }
59
- async function runCli(options) {
474
+ function printImportHelp() {
475
+ console.error(`
476
+ ai-context-inspector import \u2014 Importar bundle a otra herramienta AI
477
+
478
+ Uso:
479
+ npx @cocaxcode/ai-context-inspector import [archivo] [opciones]
480
+
481
+ Argumentos:
482
+ archivo Ruta al bundle (default: auto-detecta .aci/bundle.json)
483
+
484
+ Opciones:
485
+ -d, --dir <ruta> Directorio destino (default: cwd)
486
+ --target <tool> Herramienta destino: claude|cursor|windsurf|copilot|gemini|codex|opencode
487
+ --scope <scope> Scope: project | user
488
+ --force Sobreescribir recursos existentes
489
+ --yes Saltar confirmaci\xF3n
490
+ --only <categor\xEDas> Solo importar: mcp,skills,agents,memories,context
491
+ --secrets <modo> Modo de secretos: none | all (default: interactivo)
492
+ -h, --help Mostrar ayuda
493
+ `);
494
+ }
495
+ function parseOnlyFlag(onlyStr) {
496
+ if (!onlyStr) return void 0;
497
+ return onlyStr.split(",").map((s) => s.trim());
498
+ }
499
+ async function runExport(argv) {
500
+ const values = parseExportArgs(argv);
501
+ if (values.help) {
502
+ printExportHelp();
503
+ process.exit(0);
504
+ }
505
+ const dir = resolve(values.dir ?? process.cwd());
506
+ try {
507
+ await access(dir);
508
+ } catch {
509
+ console.error(`Error: El directorio no existe: ${dir}`);
510
+ process.exit(1);
511
+ }
512
+ const includeUser = values["include-user"] ?? false;
513
+ const onlyFlag = parseOnlyFlag(values.only);
514
+ const secretsFlag = values.secrets;
515
+ console.error(`Escaneando ${dir}...`);
516
+ const scan = await runAllScanners({
517
+ dir,
518
+ includeUser,
519
+ introspect: false,
520
+ timeout: 5e3
521
+ });
522
+ const available = [];
523
+ if (scan.mcpServers.length > 0) available.push({ category: "mcp", count: scan.mcpServers.length });
524
+ if (scan.skills.length > 0) available.push({ category: "skills", count: scan.skills.length });
525
+ if (scan.agents.length > 0) available.push({ category: "agents", count: scan.agents.length });
526
+ if (scan.memories.length > 0) available.push({ category: "memories", count: scan.memories.length });
527
+ if (scan.contextFiles.length > 0) available.push({ category: "context", count: scan.contextFiles.length });
528
+ if (available.length === 0) {
529
+ console.error("No se encontraron recursos AI para exportar.");
530
+ return;
531
+ }
532
+ let only = onlyFlag;
533
+ if (!only) {
534
+ const rl = createPromptInterface();
535
+ try {
536
+ only = await promptCategories(rl, available, "exportar");
537
+ } finally {
538
+ rl.close();
539
+ }
540
+ }
541
+ let secretsMode;
542
+ let secretDecisions;
543
+ if (secretsFlag === "none" || secretsFlag === "all") {
544
+ secretsMode = secretsFlag;
545
+ } else {
546
+ const detectedVars = detectEnvVars(scan.mcpServers);
547
+ if (detectedVars.length === 0) {
548
+ secretsMode = "none";
549
+ } else {
550
+ const rl = createPromptInterface();
551
+ try {
552
+ const result = await promptExportSecrets(rl, detectedVars);
553
+ secretsMode = result.mode;
554
+ secretDecisions = result.decisions;
555
+ } finally {
556
+ rl.close();
557
+ }
558
+ }
559
+ }
560
+ const exportOptions = {
561
+ dir,
562
+ includeUser,
563
+ only,
564
+ secrets: secretsMode,
565
+ secretDecisions
566
+ };
567
+ const bundle = await exportEcosystem(exportOptions);
568
+ const allRedacted = [];
569
+ const allIncluded = [];
570
+ for (const server of bundle.resources.mcpServers) {
571
+ allRedacted.push(...server.envVarsRedacted);
572
+ allIncluded.push(...server.envVarsIncluded);
573
+ }
574
+ printExportResult(bundle, allRedacted, allIncluded, true);
575
+ }
576
+ async function runImport(argv) {
577
+ const { values, positionals } = parseImportArgs(argv);
578
+ if (values.help) {
579
+ printImportHelp();
580
+ process.exit(0);
581
+ }
582
+ const dir = resolve(values.dir ?? process.cwd());
583
+ const bundleFile = positionals[0] ?? void 0;
584
+ const targetFlag = values.target;
585
+ const scopeFlag = values.scope;
586
+ const forceFlag = values.force ?? false;
587
+ const yesFlag = values.yes ?? false;
588
+ const onlyFlag = parseOnlyFlag(values.only);
589
+ const secretsFlag = values.secrets;
590
+ console.error("Cargando bundle...");
591
+ const bundle = await loadBundle(bundleFile, dir);
592
+ console.error(`Bundle cargado: ${bundle.sourceProject} (${bundle.createdAt})`);
593
+ const available = [];
594
+ const r = bundle.resources;
595
+ if (r.mcpServers.length > 0) available.push({ category: "mcp", count: r.mcpServers.length });
596
+ if (r.skills.length > 0) available.push({ category: "skills", count: r.skills.length });
597
+ if (r.agents.length > 0) available.push({ category: "agents", count: r.agents.length });
598
+ if (r.memories.length > 0) available.push({ category: "memories", count: r.memories.length });
599
+ if (r.contextFiles.length > 0) available.push({ category: "context", count: r.contextFiles.length });
600
+ if (available.length === 0) {
601
+ console.error("El bundle no contiene recursos para importar.");
602
+ return;
603
+ }
604
+ let only = onlyFlag;
605
+ if (!only) {
606
+ const rl = createPromptInterface();
607
+ try {
608
+ only = await promptCategories(rl, available, "importar");
609
+ } finally {
610
+ rl.close();
611
+ }
612
+ }
613
+ let target = targetFlag;
614
+ if (!target) {
615
+ const detected = await detectTargetTools(dir);
616
+ if (detected.length === 1) {
617
+ target = detected[0];
618
+ console.error(`Herramienta detectada: ${target}`);
619
+ } else {
620
+ const rl = createPromptInterface();
621
+ try {
622
+ target = await promptTargetTool(rl, detected);
623
+ } finally {
624
+ rl.close();
625
+ }
626
+ }
627
+ }
628
+ let secretValues;
629
+ if (secretsFlag !== "none" && secretsFlag !== "all") {
630
+ const envVarsIncluded = [];
631
+ const envVarsRedacted = [];
632
+ const envValues = {};
633
+ for (const server of r.mcpServers) {
634
+ envVarsIncluded.push(...server.envVarsIncluded);
635
+ envVarsRedacted.push(...server.envVarsRedacted);
636
+ if (server.config.env) {
637
+ for (const [k, v] of Object.entries(server.config.env)) {
638
+ if (server.envVarsIncluded.includes(k)) {
639
+ envValues[k] = v;
640
+ }
641
+ }
642
+ }
643
+ }
644
+ if (envVarsIncluded.length > 0 || envVarsRedacted.length > 0) {
645
+ const rl = createPromptInterface();
646
+ try {
647
+ const result2 = await promptImportSecrets(rl, { envVarsIncluded, envVarsRedacted, envValues });
648
+ if (result2.values) {
649
+ secretValues = result2.values;
650
+ }
651
+ } finally {
652
+ rl.close();
653
+ }
654
+ }
655
+ } else if (secretsFlag === "none") {
656
+ secretValues = void 0;
657
+ }
658
+ const importOptions = {
659
+ file: bundleFile,
660
+ dir,
661
+ target,
662
+ scope: scopeFlag,
663
+ force: forceFlag,
664
+ confirm: yesFlag,
665
+ only,
666
+ secrets: secretsFlag ?? "none",
667
+ secretValues
668
+ };
669
+ const plan = await planImport(bundle, importOptions);
670
+ if (!yesFlag) {
671
+ const rl = createPromptInterface();
672
+ try {
673
+ const confirmed = await promptConfirmPlan(rl, plan);
674
+ if (!confirmed) {
675
+ console.error("Importaci\xF3n cancelada.");
676
+ return;
677
+ }
678
+ } finally {
679
+ rl.close();
680
+ }
681
+ }
682
+ const result = await executeImport(plan, bundle, importOptions);
683
+ printImportResult(result);
684
+ }
685
+ async function runCli(argv) {
686
+ const subcommand = argv[0];
687
+ if (subcommand === "export") {
688
+ return runExport(argv.slice(1));
689
+ }
690
+ if (subcommand === "import") {
691
+ return runImport(argv.slice(1));
692
+ }
693
+ const options = parseCliArgs(argv);
60
694
  const dir = resolve(options.dir);
61
695
  try {
62
696
  await access(dir);
@@ -90,20 +724,35 @@ Escaneo completado en ${result.scanDuration}ms
90
724
  Warnings: ${result.warnings.length}
91
725
 
92
726
  Reporte: ${outputPath}`);
727
+ const aciPath = join(dir, ACI_DIR, ACI_BUNDLE);
728
+ try {
729
+ await access(aciPath);
730
+ const rl = createPromptInterface();
731
+ try {
732
+ const shouldImport = await promptAutoImport(rl);
733
+ if (shouldImport) {
734
+ await runImport([]);
735
+ }
736
+ } finally {
737
+ rl.close();
738
+ }
739
+ } catch {
740
+ }
93
741
  }
94
742
 
95
743
  // src/index.ts
96
744
  async function main() {
97
- const options = parseCliArgs(process.argv.slice(2));
98
- if (options.mcp) {
745
+ const argv = process.argv.slice(2);
746
+ const hasMcpFlag = argv.includes("--mcp");
747
+ if (hasMcpFlag) {
99
748
  const { StdioServerTransport } = await import("@modelcontextprotocol/sdk/server/stdio.js");
100
- const { createServer } = await import("./server-BSHVSE33.js");
749
+ const { createServer } = await import("./server-22Y7DPSM.js");
101
750
  const server = createServer();
102
751
  const transport = new StdioServerTransport();
103
752
  await server.connect(transport);
104
753
  console.error("ai-context-inspector MCP server running on stdio");
105
754
  } else {
106
- await runCli(options);
755
+ await runCli(argv);
107
756
  }
108
757
  }
109
758
  main().catch((error) => {