@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/README.md +124 -9
- package/dist/{chunk-G57UDS2C.js → chunk-3NLUFQSB.js} +966 -19
- package/dist/index.js +657 -8
- package/dist/server-22Y7DPSM.js +395 -0
- package/package.json +79 -75
- package/dist/server-BSHVSE33.js +0 -179
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-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
98
|
-
|
|
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-
|
|
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(
|
|
755
|
+
await runCli(argv);
|
|
107
756
|
}
|
|
108
757
|
}
|
|
109
758
|
main().catch((error) => {
|