@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.
@@ -1454,6 +1454,8 @@ body {
1454
1454
  .badge--orange { border-color: var(--orange); color: var(--orange); }
1455
1455
  .badge--blue { border-color: var(--blue); color: var(--blue); }
1456
1456
  .badge--pink { border-color: var(--pink); color: var(--pink); }
1457
+ .badge--link { cursor: pointer; text-decoration: none; transition: transform 0.15s, box-shadow 0.15s; }
1458
+ .badge--link:hover { transform: translateY(-1px); box-shadow: 0 2px 8px rgba(0,0,0,0.3); }
1457
1459
 
1458
1460
  /* \u2500\u2500 Stats Grid \u2500\u2500 */
1459
1461
  .stats-grid {
@@ -2135,7 +2137,7 @@ document.addEventListener('DOMContentLoaded', () => {
2135
2137
 
2136
2138
  // \u2500\u2500 Animated Counters \u2500\u2500
2137
2139
  document.querySelectorAll('.stat-number').forEach(el => {
2138
- const target = parseInt(el.getAttribute('data-target') || '0')
2140
+ const target = parseInt(el.getAttribute('data-count') || '0')
2139
2141
  if (target === 0) { el.textContent = '0'; return }
2140
2142
  let current = 0
2141
2143
  const step = Math.max(1, Math.ceil(target / 25))
@@ -2236,8 +2238,8 @@ document.addEventListener('DOMContentLoaded', () => {
2236
2238
  }
2237
2239
  })
2238
2240
 
2239
- // \u2500\u2500 Nav Links (scroll to section) \u2500\u2500
2240
- document.querySelectorAll('.nav-link[data-target]').forEach(link => {
2241
+ // \u2500\u2500 Nav Links + Header Badges (scroll to section) \u2500\u2500
2242
+ document.querySelectorAll('[data-target]').forEach(link => {
2241
2243
  link.addEventListener('click', (e) => {
2242
2244
  e.preventDefault()
2243
2245
  scrollToSection(link.getAttribute('data-target'))
@@ -2496,7 +2498,7 @@ function renderEcosystemMap(result, summary) {
2496
2498
  }
2497
2499
  }
2498
2500
  const nodeR = dimmed ? 24 : 32;
2499
- const labelY = y - nodeR - 10;
2501
+ const labelY = y - nodeR - 28;
2500
2502
  catNodes += `<g class="eco-cat-node" data-section="${cat.id}"
2501
2503
  style="cursor:pointer;opacity:${opacity}" role="button" tabindex="0">
2502
2504
  <!-- Hit area -->
@@ -2620,12 +2622,12 @@ function renderHeader(project, summary, scanDuration) {
2620
2622
  <h1>&gt; ai-context-inspector</h1>
2621
2623
  <div class="subtitle">${esc2(project.name)} &mdash; ${date} &mdash; ${scanDuration}ms</div>
2622
2624
  <div class="badges">
2623
- <span class="badge badge--accent">${summary.totalMcpServers} MCPs</span>
2624
- <span class="badge badge--green">${summary.totalTools} tools</span>
2625
- <span class="badge badge--purple">${summary.totalFiles} archivos</span>
2626
- <span class="badge badge--orange">${summary.totalSkills} skills</span>
2627
- <span class="badge badge--blue">${summary.totalAgents} agents</span>
2628
- <span class="badge badge--pink">${summary.totalMemories} memorias</span>
2625
+ <a class="badge badge--accent badge--link" data-target="section-mcp">${summary.totalMcpServers} MCPs</a>
2626
+ <a class="badge badge--green badge--link" data-target="section-mcp">${summary.totalTools} tools</a>
2627
+ <a class="badge badge--purple badge--link" data-target="section-context">${summary.totalFiles} archivos</a>
2628
+ <a class="badge badge--orange badge--link" data-target="section-skills">${summary.totalSkills} skills</a>
2629
+ <a class="badge badge--blue badge--link" data-target="section-agents">${summary.totalAgents} agents</a>
2630
+ <a class="badge badge--pink badge--link" data-target="section-memories">${summary.totalMemories} memorias</a>
2629
2631
  </div>
2630
2632
  </header>`;
2631
2633
  }
@@ -2635,44 +2637,50 @@ function renderStatsGrid(summary) {
2635
2637
  icon: "\u2699\uFE0F",
2636
2638
  value: summary.totalMcpServers,
2637
2639
  label: "MCP Servers",
2638
- color: "#00d4ff"
2640
+ color: "#00d4ff",
2641
+ section: "section-mcp"
2639
2642
  },
2640
2643
  {
2641
2644
  icon: "\u{1F6E0}\uFE0F",
2642
2645
  value: summary.totalTools,
2643
2646
  label: "MCP Tools",
2644
- color: "#00e676"
2647
+ color: "#00e676",
2648
+ section: "section-mcp"
2645
2649
  },
2646
2650
  {
2647
2651
  icon: "\u{1F4C4}",
2648
2652
  value: summary.totalFiles,
2649
2653
  label: "Archivos AI",
2650
- color: "#b388ff"
2654
+ color: "#b388ff",
2655
+ section: "section-context"
2651
2656
  },
2652
2657
  {
2653
2658
  icon: "\u26A1",
2654
2659
  value: summary.totalSkills,
2655
2660
  label: "Skills",
2656
- color: "#ffab40"
2661
+ color: "#ffab40",
2662
+ section: "section-skills"
2657
2663
  },
2658
2664
  {
2659
2665
  icon: "\u{1F916}",
2660
2666
  value: summary.totalAgents,
2661
2667
  label: "Agents",
2662
- color: "#4285f4"
2668
+ color: "#4285f4",
2669
+ section: "section-agents"
2663
2670
  },
2664
2671
  {
2665
2672
  icon: "\u{1F9E0}",
2666
2673
  value: summary.totalMemories,
2667
2674
  label: "Memorias",
2668
- color: "#ff80ab"
2675
+ color: "#ff80ab",
2676
+ section: "section-memories"
2669
2677
  }
2670
2678
  ];
2671
2679
  const cards = stats.map(
2672
2680
  (s) => `
2673
- <div class="stat-card" style="--stat-color: ${s.color}">
2681
+ <div class="stat-card" data-target="${s.section}" style="--stat-color: ${s.color};cursor:pointer">
2674
2682
  <span class="stat-icon">${s.icon}</span>
2675
- <span class="stat-number" data-target="${s.value}">0</span>
2683
+ <span class="stat-number" data-count="${s.value}">0</span>
2676
2684
  <span class="stat-label">${s.label}</span>
2677
2685
  </div>`
2678
2686
  ).join("");
@@ -3022,9 +3030,948 @@ function escHtml(str) {
3022
3030
  return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
3023
3031
  }
3024
3032
 
3033
+ // src/ecosystem/types.ts
3034
+ var ACI_DIR = ".aci";
3035
+ var ACI_BUNDLE = "bundle.json";
3036
+ var ACI_README = "README.md";
3037
+
3038
+ // src/ecosystem/secrets.ts
3039
+ var SENSITIVE_VAR_REGEX = /key|token|secret|password|credential|auth/i;
3040
+ var VAR_REFERENCE_REGEX = /^\$\{.+\}$/;
3041
+ var ARG_VAR_REGEX = /\$\{([^}]+)\}/g;
3042
+ function isSensitiveVar(varName) {
3043
+ return SENSITIVE_VAR_REGEX.test(varName);
3044
+ }
3045
+ function detectEnvVars(servers) {
3046
+ const vars = [];
3047
+ const seen = /* @__PURE__ */ new Set();
3048
+ for (const server of servers) {
3049
+ if (server.config.env) {
3050
+ for (const [varName, value] of Object.entries(server.config.env)) {
3051
+ const key = `${server.name}:${varName}`;
3052
+ if (seen.has(key)) continue;
3053
+ seen.add(key);
3054
+ vars.push({
3055
+ serverName: server.name,
3056
+ varName,
3057
+ value,
3058
+ isSensitive: isSensitiveVar(varName)
3059
+ });
3060
+ }
3061
+ }
3062
+ if (server.config.args) {
3063
+ for (const arg of server.config.args) {
3064
+ let match;
3065
+ while ((match = ARG_VAR_REGEX.exec(arg)) !== null) {
3066
+ const varName = match[1];
3067
+ const key = `${server.name}:${varName}`;
3068
+ if (seen.has(key)) continue;
3069
+ seen.add(key);
3070
+ vars.push({
3071
+ serverName: server.name,
3072
+ varName,
3073
+ value: `\${${varName}}`,
3074
+ isSensitive: isSensitiveVar(varName)
3075
+ });
3076
+ }
3077
+ }
3078
+ }
3079
+ }
3080
+ return vars;
3081
+ }
3082
+ function redactValue(varName) {
3083
+ return `\${${varName}}`;
3084
+ }
3085
+ function applySecretsPolicy(env, mode, customDecisions) {
3086
+ if (!env || Object.keys(env).length === 0) {
3087
+ return { env: void 0, redacted: [], included: [] };
3088
+ }
3089
+ const processed = {};
3090
+ const redacted = [];
3091
+ const included = [];
3092
+ for (const [varName, value] of Object.entries(env)) {
3093
+ if (VAR_REFERENCE_REGEX.test(value)) {
3094
+ processed[varName] = value;
3095
+ included.push(varName);
3096
+ continue;
3097
+ }
3098
+ if (mode === "none") {
3099
+ processed[varName] = redactValue(varName);
3100
+ redacted.push(varName);
3101
+ } else if (mode === "all") {
3102
+ processed[varName] = value;
3103
+ included.push(varName);
3104
+ } else {
3105
+ const include = customDecisions?.[varName] ?? false;
3106
+ if (include) {
3107
+ processed[varName] = value;
3108
+ included.push(varName);
3109
+ } else {
3110
+ processed[varName] = redactValue(varName);
3111
+ redacted.push(varName);
3112
+ }
3113
+ }
3114
+ }
3115
+ return { env: processed, redacted, included };
3116
+ }
3117
+
3118
+ // src/ecosystem/export.ts
3119
+ import { readFile as readFile7, writeFile, readdir as readdir5, stat as stat5, mkdir } from "fs/promises";
3120
+ import { join as join7, basename as basename3, dirname, relative as relative2 } from "path";
3121
+ import { createHash } from "crypto";
3122
+ async function exportEcosystem(options) {
3123
+ const scan = await runAllScanners({
3124
+ dir: options.dir,
3125
+ includeUser: options.includeUser,
3126
+ introspect: false,
3127
+ timeout: 5e3
3128
+ });
3129
+ const resources = await buildBundleResources(scan, options);
3130
+ const checksum = computeChecksum(resources);
3131
+ const warnings = [];
3132
+ if (options.secrets === "all") {
3133
+ warnings.push("Secretos incluidos sin redactar. No compartas este bundle publicamente.");
3134
+ }
3135
+ for (const w of scan.warnings) {
3136
+ warnings.push(`[${w.scanner}] ${w.message}`);
3137
+ }
3138
+ const bundle = {
3139
+ version: 1,
3140
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
3141
+ sourceProject: scan.project.name,
3142
+ checksum,
3143
+ warnings,
3144
+ resources
3145
+ };
3146
+ const aciDir = join7(options.dir, ACI_DIR);
3147
+ await writeBundle(bundle, aciDir);
3148
+ const readme = generateReadme(bundle);
3149
+ await writeFile(join7(aciDir, ACI_README), readme, "utf-8");
3150
+ await ensureGitignore(options.dir);
3151
+ return bundle;
3152
+ }
3153
+ async function buildBundleResources(scan, options) {
3154
+ const categories = options.only ?? ["mcp", "skills", "agents", "memories", "context"];
3155
+ const projectDir = scan.project.path;
3156
+ const resources = {
3157
+ mcpServers: [],
3158
+ skills: [],
3159
+ agents: [],
3160
+ memories: [],
3161
+ contextFiles: []
3162
+ };
3163
+ if (categories.includes("mcp")) {
3164
+ resources.mcpServers = buildMcpServers(scan, options);
3165
+ }
3166
+ if (categories.includes("skills")) {
3167
+ resources.skills = await buildSkills(scan);
3168
+ }
3169
+ if (categories.includes("agents")) {
3170
+ resources.agents = await buildAgents(scan);
3171
+ }
3172
+ if (categories.includes("memories")) {
3173
+ resources.memories = await buildMemories(scan, projectDir);
3174
+ }
3175
+ if (categories.includes("context")) {
3176
+ resources.contextFiles = await buildContextFiles(scan);
3177
+ }
3178
+ return resources;
3179
+ }
3180
+ function buildMcpServers(scan, options) {
3181
+ const servers = [];
3182
+ for (const server of scan.mcpServers) {
3183
+ const { env, redacted, included } = applySecretsPolicy(
3184
+ server.config.env,
3185
+ options.secrets,
3186
+ options.secretDecisions
3187
+ );
3188
+ const scope = server.source === "user" || server.source === "desktop" ? "user" : "project";
3189
+ servers.push({
3190
+ name: server.name,
3191
+ scope,
3192
+ config: {
3193
+ transport: server.config.transport,
3194
+ command: server.config.command,
3195
+ args: server.config.args,
3196
+ env,
3197
+ url: server.config.url
3198
+ },
3199
+ envVarsRedacted: redacted,
3200
+ envVarsIncluded: included
3201
+ });
3202
+ }
3203
+ return servers;
3204
+ }
3205
+ async function buildSkills(scan) {
3206
+ const skills = [];
3207
+ for (const skill of scan.skills) {
3208
+ try {
3209
+ const content = await readFile7(skill.path, "utf-8");
3210
+ const dirName = basename3(dirname(skill.path));
3211
+ skills.push({
3212
+ name: skill.name,
3213
+ scope: skill.scope,
3214
+ dirName,
3215
+ content,
3216
+ description: skill.description,
3217
+ triggers: skill.triggers
3218
+ });
3219
+ } catch {
3220
+ }
3221
+ }
3222
+ return skills;
3223
+ }
3224
+ async function buildAgents(scan) {
3225
+ const agents = [];
3226
+ for (const agent of scan.agents) {
3227
+ try {
3228
+ const content = await readFile7(agent.path, "utf-8");
3229
+ const fileName = basename3(agent.path);
3230
+ agents.push({
3231
+ name: agent.name,
3232
+ scope: agent.scope,
3233
+ fileName,
3234
+ content,
3235
+ description: agent.description,
3236
+ model: agent.model
3237
+ });
3238
+ } catch {
3239
+ }
3240
+ }
3241
+ return agents;
3242
+ }
3243
+ async function buildMemories(scan, projectDir) {
3244
+ const memories = [];
3245
+ for (const memory of scan.memories) {
3246
+ if (memory.source !== "filesystem") continue;
3247
+ if (!memory.path) continue;
3248
+ const files = [];
3249
+ try {
3250
+ if (memory.type === "atl" || memory.type === "openspec") {
3251
+ const cleanPath = memory.path.endsWith("/") ? memory.path.slice(0, -1) : memory.path;
3252
+ const absDir = join7(projectDir, cleanPath);
3253
+ const dirFiles = await readDirRecursive(absDir, absDir);
3254
+ files.push(...dirFiles);
3255
+ } else if (memory.type === "claude-memory") {
3256
+ const content = await readFile7(memory.path, "utf-8");
3257
+ files.push({ relativePath: basename3(memory.path), content });
3258
+ } else if (memory.type === "agent-memory") {
3259
+ const s = await stat5(memory.path);
3260
+ if (s.isDirectory()) {
3261
+ const entries = await readdir5(memory.path);
3262
+ for (const entry of entries) {
3263
+ if (!entry.endsWith(".md")) continue;
3264
+ const filePath = join7(memory.path, entry);
3265
+ const content = await readFile7(filePath, "utf-8");
3266
+ files.push({ relativePath: entry, content });
3267
+ }
3268
+ } else {
3269
+ const content = await readFile7(memory.path, "utf-8");
3270
+ files.push({ relativePath: basename3(memory.path), content });
3271
+ }
3272
+ }
3273
+ } catch {
3274
+ }
3275
+ const scope = memory.type === "claude-memory" || memory.type === "agent-memory" ? "user" : "project";
3276
+ if (files.length > 0) {
3277
+ memories.push({ type: memory.type, scope, files });
3278
+ }
3279
+ }
3280
+ return memories;
3281
+ }
3282
+ async function buildContextFiles(scan) {
3283
+ const contextFiles = [];
3284
+ for (const cf of scan.contextFiles) {
3285
+ if (cf.type === "directory" && cf.children) {
3286
+ for (const child of cf.children) {
3287
+ if (child.type === "file") {
3288
+ try {
3289
+ const content = await readFile7(child.absolutePath, "utf-8");
3290
+ contextFiles.push({
3291
+ path: child.path,
3292
+ scope: child.scope,
3293
+ tool: child.tool,
3294
+ content
3295
+ });
3296
+ } catch {
3297
+ }
3298
+ }
3299
+ }
3300
+ } else if (cf.type === "file") {
3301
+ try {
3302
+ const content = await readFile7(cf.absolutePath, "utf-8");
3303
+ contextFiles.push({
3304
+ path: cf.path,
3305
+ scope: cf.scope,
3306
+ tool: cf.tool,
3307
+ content
3308
+ });
3309
+ } catch {
3310
+ }
3311
+ }
3312
+ }
3313
+ return contextFiles;
3314
+ }
3315
+ async function readDirRecursive(dirPath, basePath) {
3316
+ const files = [];
3317
+ try {
3318
+ const entries = await readdir5(dirPath, { withFileTypes: true });
3319
+ for (const entry of entries) {
3320
+ const fullPath = join7(dirPath, entry.name);
3321
+ if (entry.isDirectory()) {
3322
+ const subFiles = await readDirRecursive(fullPath, basePath);
3323
+ files.push(...subFiles);
3324
+ } else if (entry.isFile()) {
3325
+ try {
3326
+ const content = await readFile7(fullPath, "utf-8");
3327
+ files.push({
3328
+ relativePath: relative2(basePath, fullPath),
3329
+ content
3330
+ });
3331
+ } catch {
3332
+ }
3333
+ }
3334
+ }
3335
+ } catch {
3336
+ }
3337
+ return files;
3338
+ }
3339
+ function computeChecksum(resources) {
3340
+ const json = JSON.stringify(resources, Object.keys(resources).sort());
3341
+ return createHash("sha256").update(json).digest("hex");
3342
+ }
3343
+ function verifyChecksum(bundle) {
3344
+ const computed = computeChecksum(bundle.resources);
3345
+ return computed === bundle.checksum;
3346
+ }
3347
+ async function writeBundle(bundle, dir) {
3348
+ await mkdir(dir, { recursive: true });
3349
+ const filePath = join7(dir, ACI_BUNDLE);
3350
+ const json = JSON.stringify(bundle, null, 2);
3351
+ await writeFile(filePath, json, "utf-8");
3352
+ return filePath;
3353
+ }
3354
+ function generateReadme(bundle) {
3355
+ const r = bundle.resources;
3356
+ const lines = [
3357
+ "# ACI Export Bundle",
3358
+ "",
3359
+ `Proyecto: **${bundle.sourceProject}**`,
3360
+ `Fecha: ${bundle.createdAt}`,
3361
+ `Version: ${bundle.version}`,
3362
+ "",
3363
+ "## Contenido",
3364
+ "",
3365
+ `| Categoria | Cantidad |`,
3366
+ `|-----------|----------|`,
3367
+ `| MCP Servers | ${r.mcpServers.length} |`,
3368
+ `| Skills | ${r.skills.length} |`,
3369
+ `| Agents | ${r.agents.length} |`,
3370
+ `| Memories | ${r.memories.length} |`,
3371
+ `| Context Files | ${r.contextFiles.length} |`,
3372
+ ""
3373
+ ];
3374
+ if (r.mcpServers.length > 0) {
3375
+ lines.push("## MCP Servers", "");
3376
+ for (const s of r.mcpServers) {
3377
+ lines.push(`- **${s.name}** (${s.config.transport}, ${s.scope})`);
3378
+ if (s.envVarsRedacted.length > 0) {
3379
+ lines.push(` - Redactadas: ${s.envVarsRedacted.join(", ")}`);
3380
+ }
3381
+ }
3382
+ lines.push("");
3383
+ }
3384
+ if (r.skills.length > 0) {
3385
+ lines.push("## Skills", "");
3386
+ for (const s of r.skills) {
3387
+ lines.push(`- **${s.name}** \u2014 ${s.description ?? "sin descripcion"}`);
3388
+ }
3389
+ lines.push("");
3390
+ }
3391
+ if (r.agents.length > 0) {
3392
+ lines.push("## Agents", "");
3393
+ for (const a of r.agents) {
3394
+ lines.push(`- **${a.name}** \u2014 ${a.description ?? "sin descripcion"}`);
3395
+ }
3396
+ lines.push("");
3397
+ }
3398
+ if (r.contextFiles.length > 0) {
3399
+ lines.push("## Context Files", "");
3400
+ for (const cf of r.contextFiles) {
3401
+ lines.push(`- \`${cf.path}\` (${cf.tool})`);
3402
+ }
3403
+ lines.push("");
3404
+ }
3405
+ if (bundle.warnings.length > 0) {
3406
+ lines.push("## Advertencias", "");
3407
+ for (const w of bundle.warnings) {
3408
+ lines.push(`- ${w}`);
3409
+ }
3410
+ lines.push("");
3411
+ }
3412
+ lines.push("---", "Generado por [@cocaxcode/ai-context-inspector](https://github.com/cocaxcode/ai-context-inspector)", "");
3413
+ return lines.join("\n");
3414
+ }
3415
+ async function ensureGitignore(dir) {
3416
+ const gitignorePath = join7(dir, ".gitignore");
3417
+ const entry = ".aci/";
3418
+ let content = "";
3419
+ try {
3420
+ content = await readFile7(gitignorePath, "utf-8");
3421
+ } catch {
3422
+ }
3423
+ const lines = content.split("\n");
3424
+ if (lines.some((line) => line.trim() === entry)) {
3425
+ return false;
3426
+ }
3427
+ const newContent = content.length > 0 && !content.endsWith("\n") ? content + "\n" + entry + "\n" : content + entry + "\n";
3428
+ await writeFile(gitignorePath, newContent, "utf-8");
3429
+ return true;
3430
+ }
3431
+
3432
+ // src/ecosystem/detect-target.ts
3433
+ import { access } from "fs/promises";
3434
+ import { join as join8 } from "path";
3435
+
3436
+ // src/ecosystem/target-map.ts
3437
+ var TARGET_CONFIGS = {
3438
+ claude: {
3439
+ mcpConfigPath: ".mcp.json",
3440
+ mcpConfigFormat: "flat",
3441
+ contextFilePath: "CLAUDE.md",
3442
+ rulesDir: null,
3443
+ skillsDir: ".claude/skills",
3444
+ agentsDir: ".claude/agents"
3445
+ },
3446
+ cursor: {
3447
+ mcpConfigPath: ".cursor/mcp.json",
3448
+ mcpConfigFormat: "flat",
3449
+ contextFilePath: ".cursorrules",
3450
+ rulesDir: ".cursor/rules",
3451
+ skillsDir: null,
3452
+ agentsDir: null
3453
+ },
3454
+ windsurf: {
3455
+ mcpConfigPath: ".mcp.json",
3456
+ mcpConfigFormat: "flat",
3457
+ contextFilePath: ".windsurfrules",
3458
+ rulesDir: ".windsurf/rules",
3459
+ skillsDir: null,
3460
+ agentsDir: null
3461
+ },
3462
+ copilot: {
3463
+ mcpConfigPath: ".vscode/mcp.json",
3464
+ mcpConfigFormat: "flat",
3465
+ contextFilePath: ".github/copilot-instructions.md",
3466
+ rulesDir: ".github/instructions",
3467
+ skillsDir: null,
3468
+ agentsDir: ".github/agents"
3469
+ },
3470
+ gemini: {
3471
+ mcpConfigPath: ".gemini/settings.json",
3472
+ mcpConfigFormat: "nested",
3473
+ contextFilePath: "GEMINI.md",
3474
+ rulesDir: ".gemini/rules",
3475
+ skillsDir: null,
3476
+ agentsDir: null
3477
+ },
3478
+ codex: {
3479
+ mcpConfigPath: ".mcp.json",
3480
+ mcpConfigFormat: "flat",
3481
+ contextFilePath: "AGENTS.md",
3482
+ rulesDir: ".codex/rules",
3483
+ skillsDir: null,
3484
+ agentsDir: null
3485
+ },
3486
+ opencode: {
3487
+ mcpConfigPath: "opencode.json",
3488
+ mcpConfigFormat: "nested",
3489
+ contextFilePath: "OPENCODE.md",
3490
+ rulesDir: ".opencode/rules",
3491
+ skillsDir: null,
3492
+ agentsDir: null
3493
+ }
3494
+ };
3495
+ var TOOL_MARKERS = {
3496
+ claude: ["CLAUDE.md", ".claude", ".mcp.json"],
3497
+ cursor: [".cursorrules", ".cursor"],
3498
+ windsurf: [".windsurfrules", ".windsurf"],
3499
+ copilot: [
3500
+ ".github/copilot-instructions.md",
3501
+ ".github/agents",
3502
+ ".vscode/mcp.json"
3503
+ ],
3504
+ gemini: ["GEMINI.md", ".gemini"],
3505
+ codex: ["AGENTS.md", ".codex"],
3506
+ opencode: ["OPENCODE.md", ".opencode", "opencode.json"]
3507
+ };
3508
+ var PRIMARY_CONTEXT_MAP = {
3509
+ "CLAUDE.md": "claude",
3510
+ ".cursorrules": "cursor",
3511
+ ".windsurfrules": "windsurf",
3512
+ ".github/copilot-instructions.md": "copilot",
3513
+ "GEMINI.md": "gemini",
3514
+ "AGENTS.md": "codex",
3515
+ "OPENCODE.md": "opencode"
3516
+ };
3517
+
3518
+ // src/ecosystem/detect-target.ts
3519
+ async function detectTargetTools(dir) {
3520
+ const results = [];
3521
+ for (const [target, markers] of Object.entries(TOOL_MARKERS)) {
3522
+ let count = 0;
3523
+ for (const marker of markers) {
3524
+ try {
3525
+ await access(join8(dir, marker));
3526
+ count++;
3527
+ } catch {
3528
+ }
3529
+ }
3530
+ if (count > 0) {
3531
+ results.push({ target, count });
3532
+ }
3533
+ }
3534
+ results.sort((a, b) => b.count - a.count);
3535
+ return results.map((r) => r.target);
3536
+ }
3537
+
3538
+ // src/ecosystem/import.ts
3539
+ import { readFile as readFile8, writeFile as writeFile2, mkdir as mkdir2, access as access2 } from "fs/promises";
3540
+ import { join as join9, dirname as dirname2 } from "path";
3541
+ import { homedir as homedir6 } from "os";
3542
+ async function loadBundle(filePath, dir) {
3543
+ const resolvedPath = filePath ?? join9(dir ?? ".", ACI_DIR, ACI_BUNDLE);
3544
+ let raw;
3545
+ try {
3546
+ raw = await readFile8(resolvedPath, "utf-8");
3547
+ } catch {
3548
+ throw new Error(`Bundle no encontrado: ${resolvedPath}`);
3549
+ }
3550
+ let bundle;
3551
+ try {
3552
+ bundle = JSON.parse(raw);
3553
+ } catch {
3554
+ throw new Error(`Bundle no es JSON valido: ${resolvedPath}`);
3555
+ }
3556
+ if (bundle.version !== 1) {
3557
+ throw new Error(`Version no soportada: ${bundle.version}. Se requiere version 1.`);
3558
+ }
3559
+ if (!verifyChecksum(bundle)) {
3560
+ throw new Error("Checksum invalido: el bundle puede estar corrupto o modificado.");
3561
+ }
3562
+ return bundle;
3563
+ }
3564
+ async function planImport(bundle, options) {
3565
+ let target;
3566
+ if (options.target) {
3567
+ target = options.target;
3568
+ } else {
3569
+ const detected = await detectTargetTools(options.dir);
3570
+ if (detected.length === 0) {
3571
+ throw new Error("No se detecto ninguna herramienta AI. Usa --target para especificar una.");
3572
+ }
3573
+ target = detected[0];
3574
+ }
3575
+ const config = TARGET_CONFIGS[target];
3576
+ const categories = options.only ?? ["mcp", "skills", "agents", "memories", "context"];
3577
+ const actions = [];
3578
+ const pendingEnvVars = [];
3579
+ if (categories.includes("mcp")) {
3580
+ for (const server of bundle.resources.mcpServers) {
3581
+ const scope = resolveScope(server.scope, options.scope);
3582
+ const basePath = getScopedBasePath(options.dir, scope);
3583
+ const mcpPath = join9(basePath, config.mcpConfigPath);
3584
+ const conflict = await checkMcpConflict(
3585
+ server.name,
3586
+ server.config,
3587
+ mcpPath,
3588
+ config.mcpConfigFormat
3589
+ );
3590
+ let action;
3591
+ let reason;
3592
+ if (conflict === "missing") {
3593
+ action = "install";
3594
+ } else if (conflict === "same") {
3595
+ action = "skip";
3596
+ reason = "Misma configuracion ya existe";
3597
+ } else {
3598
+ if (options.force) {
3599
+ action = "overwrite";
3600
+ reason = "Configuracion diferente (--force)";
3601
+ } else {
3602
+ action = "skip";
3603
+ reason = "Configuracion diferente (usa --force para sobreescribir)";
3604
+ }
3605
+ }
3606
+ actions.push({
3607
+ category: "mcp",
3608
+ name: server.name,
3609
+ action,
3610
+ reason,
3611
+ targetPath: mcpPath
3612
+ });
3613
+ for (const varName of server.envVarsRedacted) {
3614
+ if (!pendingEnvVars.includes(varName)) {
3615
+ pendingEnvVars.push(varName);
3616
+ }
3617
+ }
3618
+ }
3619
+ }
3620
+ if (categories.includes("skills")) {
3621
+ for (const skill of bundle.resources.skills) {
3622
+ const scope = resolveScope(skill.scope, options.scope);
3623
+ const basePath = getScopedBasePath(options.dir, scope);
3624
+ if (config.skillsDir) {
3625
+ const skillPath = join9(basePath, config.skillsDir, skill.dirName, "SKILL.md");
3626
+ const exists = await fileExists(skillPath);
3627
+ if (exists && !options.force) {
3628
+ actions.push({
3629
+ category: "skills",
3630
+ name: skill.name,
3631
+ action: "skip",
3632
+ reason: "Skill ya existe (usa --force para sobreescribir)",
3633
+ targetPath: skillPath
3634
+ });
3635
+ } else {
3636
+ actions.push({
3637
+ category: "skills",
3638
+ name: skill.name,
3639
+ action: exists ? "overwrite" : "install",
3640
+ reason: exists ? "Sobreescribiendo skill existente (--force)" : void 0,
3641
+ targetPath: skillPath
3642
+ });
3643
+ }
3644
+ } else if (config.rulesDir) {
3645
+ const rulePath = join9(basePath, config.rulesDir, `${skill.dirName}.md`);
3646
+ const exists = await fileExists(rulePath);
3647
+ if (exists && !options.force) {
3648
+ actions.push({
3649
+ category: "skills",
3650
+ name: skill.name,
3651
+ action: "skip",
3652
+ reason: "Regla ya existe (usa --force para sobreescribir)",
3653
+ targetPath: rulePath
3654
+ });
3655
+ } else {
3656
+ actions.push({
3657
+ category: "skills",
3658
+ name: skill.name,
3659
+ action: exists ? "overwrite" : "install",
3660
+ reason: exists ? "Sobreescribiendo como regla (--force)" : "Instalando como regla",
3661
+ targetPath: rulePath
3662
+ });
3663
+ }
3664
+ } else {
3665
+ actions.push({
3666
+ category: "skills",
3667
+ name: skill.name,
3668
+ action: "unsupported",
3669
+ reason: `${target} no soporta skills ni rules`,
3670
+ targetPath: ""
3671
+ });
3672
+ }
3673
+ }
3674
+ }
3675
+ if (categories.includes("agents")) {
3676
+ for (const agent of bundle.resources.agents) {
3677
+ const scope = resolveScope(agent.scope, options.scope);
3678
+ const basePath = getScopedBasePath(options.dir, scope);
3679
+ if (config.agentsDir) {
3680
+ const agentPath = join9(basePath, config.agentsDir, agent.fileName);
3681
+ const exists = await fileExists(agentPath);
3682
+ if (exists && !options.force) {
3683
+ actions.push({
3684
+ category: "agents",
3685
+ name: agent.name,
3686
+ action: "skip",
3687
+ reason: "Agent ya existe (usa --force para sobreescribir)",
3688
+ targetPath: agentPath
3689
+ });
3690
+ } else {
3691
+ actions.push({
3692
+ category: "agents",
3693
+ name: agent.name,
3694
+ action: exists ? "overwrite" : "install",
3695
+ reason: exists ? "Sobreescribiendo agent existente (--force)" : void 0,
3696
+ targetPath: agentPath
3697
+ });
3698
+ }
3699
+ } else {
3700
+ actions.push({
3701
+ category: "agents",
3702
+ name: agent.name,
3703
+ action: "unsupported",
3704
+ reason: `${target} no soporta agents`,
3705
+ targetPath: ""
3706
+ });
3707
+ }
3708
+ }
3709
+ }
3710
+ if (categories.includes("context")) {
3711
+ for (const cf of bundle.resources.contextFiles) {
3712
+ const mappedPath = mapContextFilePath(cf.path, target);
3713
+ if (mappedPath) {
3714
+ const scope = resolveScope(cf.scope, options.scope);
3715
+ const basePath = getScopedBasePath(options.dir, scope);
3716
+ const fullPath = join9(basePath, mappedPath);
3717
+ const exists = await fileExists(fullPath);
3718
+ if (exists && !options.force) {
3719
+ actions.push({
3720
+ category: "context",
3721
+ name: cf.path,
3722
+ action: "skip",
3723
+ reason: "Archivo de contexto ya existe (usa --force para sobreescribir)",
3724
+ targetPath: fullPath
3725
+ });
3726
+ } else {
3727
+ actions.push({
3728
+ category: "context",
3729
+ name: cf.path,
3730
+ action: exists ? "overwrite" : "install",
3731
+ reason: exists ? "Sobreescribiendo contexto (--force)" : void 0,
3732
+ targetPath: fullPath
3733
+ });
3734
+ }
3735
+ } else {
3736
+ actions.push({
3737
+ category: "context",
3738
+ name: cf.path,
3739
+ action: "unsupported",
3740
+ reason: `Sin equivalente en ${target}`,
3741
+ targetPath: ""
3742
+ });
3743
+ }
3744
+ }
3745
+ }
3746
+ if (categories.includes("memories")) {
3747
+ for (const memory of bundle.resources.memories) {
3748
+ const scope = resolveScope(memory.scope, options.scope);
3749
+ const basePath = getScopedBasePath(options.dir, scope);
3750
+ for (const file of memory.files) {
3751
+ const filePath = join9(basePath, file.relativePath);
3752
+ const exists = await fileExists(filePath);
3753
+ actions.push({
3754
+ category: "memories",
3755
+ name: `${memory.type}/${file.relativePath}`,
3756
+ action: exists && !options.force ? "skip" : exists ? "overwrite" : "install",
3757
+ reason: exists && !options.force ? "Archivo de memoria ya existe" : exists ? "Sobreescribiendo memoria (--force)" : void 0,
3758
+ targetPath: filePath
3759
+ });
3760
+ }
3761
+ }
3762
+ }
3763
+ const summary = {
3764
+ install: actions.filter((a) => a.action === "install").length,
3765
+ skip: actions.filter((a) => a.action === "skip").length,
3766
+ overwrite: actions.filter((a) => a.action === "overwrite").length,
3767
+ unsupported: actions.filter((a) => a.action === "unsupported").length
3768
+ };
3769
+ return { target, actions, pendingEnvVars, summary };
3770
+ }
3771
+ async function executeImport(plan, bundle, options) {
3772
+ const config = TARGET_CONFIGS[plan.target];
3773
+ const installed = [];
3774
+ const skipped = [];
3775
+ const unsupported = [];
3776
+ for (const action of plan.actions) {
3777
+ if (action.action === "unsupported") {
3778
+ unsupported.push(action);
3779
+ continue;
3780
+ }
3781
+ if (action.action === "skip") {
3782
+ skipped.push(action);
3783
+ continue;
3784
+ }
3785
+ try {
3786
+ switch (action.category) {
3787
+ case "mcp": {
3788
+ const server = bundle.resources.mcpServers.find((s) => s.name === action.name);
3789
+ if (server) {
3790
+ await installMcpServer(server, action.targetPath, config.mcpConfigFormat, options.secretValues);
3791
+ }
3792
+ break;
3793
+ }
3794
+ case "skills": {
3795
+ const skill = bundle.resources.skills.find((s) => s.name === action.name);
3796
+ if (skill) {
3797
+ await installSkill(skill, options.dir, config, options.scope, options.force);
3798
+ }
3799
+ break;
3800
+ }
3801
+ case "agents": {
3802
+ const agent = bundle.resources.agents.find((a) => a.name === action.name);
3803
+ if (agent) {
3804
+ await installAgent(agent, options.dir, config, options.scope, options.force);
3805
+ }
3806
+ break;
3807
+ }
3808
+ case "context": {
3809
+ const cf = bundle.resources.contextFiles.find((c) => c.path === action.name);
3810
+ if (cf) {
3811
+ const mappedPath = mapContextFilePath(cf.path, plan.target);
3812
+ if (mappedPath) {
3813
+ await installContextFile(cf, options.dir, mappedPath);
3814
+ }
3815
+ }
3816
+ break;
3817
+ }
3818
+ case "memories": {
3819
+ const [memType, ...pathParts] = action.name.split("/");
3820
+ const relPath = pathParts.join("/");
3821
+ const memory = bundle.resources.memories.find((m) => m.type === memType);
3822
+ if (memory) {
3823
+ const file = memory.files.find((f) => f.relativePath === relPath);
3824
+ if (file) {
3825
+ await installMemory({ ...memory, files: [file] }, options.dir);
3826
+ }
3827
+ }
3828
+ break;
3829
+ }
3830
+ }
3831
+ installed.push(action);
3832
+ } catch {
3833
+ skipped.push({ ...action, reason: "Error al instalar" });
3834
+ }
3835
+ }
3836
+ await ensureGitignore(options.dir);
3837
+ return {
3838
+ installed,
3839
+ skipped,
3840
+ unsupported,
3841
+ pendingEnvVars: plan.pendingEnvVars
3842
+ };
3843
+ }
3844
+ async function checkMcpConflict(serverName, serverConfig, mcpConfigPath, format) {
3845
+ let raw;
3846
+ try {
3847
+ raw = await readFile8(mcpConfigPath, "utf-8");
3848
+ } catch {
3849
+ return "missing";
3850
+ }
3851
+ let parsed;
3852
+ try {
3853
+ parsed = JSON.parse(raw);
3854
+ } catch {
3855
+ return "missing";
3856
+ }
3857
+ const servers = format === "flat" ? parsed.mcpServers : parsed.mcpServers;
3858
+ if (!servers || !(serverName in servers)) {
3859
+ return "missing";
3860
+ }
3861
+ const existing = servers[serverName];
3862
+ const keysToCompare = ["command", "args", "url", "transport"];
3863
+ for (const key of keysToCompare) {
3864
+ const a = serverConfig[key];
3865
+ const b = existing[key];
3866
+ if (JSON.stringify(a) !== JSON.stringify(b)) {
3867
+ return "different";
3868
+ }
3869
+ }
3870
+ return "same";
3871
+ }
3872
+ async function installMcpServer(server, mcpConfigPath, format, secretValues) {
3873
+ let parsed = {};
3874
+ try {
3875
+ const raw = await readFile8(mcpConfigPath, "utf-8");
3876
+ parsed = JSON.parse(raw);
3877
+ } catch {
3878
+ }
3879
+ if (!parsed.mcpServers || typeof parsed.mcpServers !== "object") {
3880
+ parsed.mcpServers = {};
3881
+ }
3882
+ const entry = {};
3883
+ if (server.config.command) entry.command = server.config.command;
3884
+ if (server.config.args) entry.args = server.config.args;
3885
+ if (server.config.url) entry.url = server.config.url;
3886
+ if (server.config.transport && server.config.transport !== "stdio") {
3887
+ entry.transport = server.config.transport;
3888
+ }
3889
+ if (server.config.env) {
3890
+ const env = { ...server.config.env };
3891
+ if (secretValues) {
3892
+ for (const [varName, value] of Object.entries(secretValues)) {
3893
+ if (varName in env && value !== null) {
3894
+ env[varName] = value;
3895
+ }
3896
+ }
3897
+ }
3898
+ entry.env = env;
3899
+ }
3900
+ ;
3901
+ parsed.mcpServers[server.name] = entry;
3902
+ await mkdir2(dirname2(mcpConfigPath), { recursive: true });
3903
+ await writeFile2(mcpConfigPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
3904
+ }
3905
+ async function installSkill(skill, dir, targetConfig, scope, force) {
3906
+ const effectiveScope = resolveScope(skill.scope, scope);
3907
+ const basePath = getScopedBasePath(dir, effectiveScope);
3908
+ if (targetConfig.skillsDir) {
3909
+ const skillDir = join9(basePath, targetConfig.skillsDir, skill.dirName);
3910
+ const skillPath = join9(skillDir, "SKILL.md");
3911
+ await mkdir2(skillDir, { recursive: true });
3912
+ await writeFile2(skillPath, skill.content, "utf-8");
3913
+ } else if (targetConfig.rulesDir) {
3914
+ const rulesDir = join9(basePath, targetConfig.rulesDir);
3915
+ const rulePath = join9(rulesDir, `${skill.dirName}.md`);
3916
+ await mkdir2(rulesDir, { recursive: true });
3917
+ await writeFile2(rulePath, skill.content, "utf-8");
3918
+ }
3919
+ }
3920
+ async function installAgent(agent, dir, targetConfig, scope, force) {
3921
+ if (!targetConfig.agentsDir) return;
3922
+ const effectiveScope = resolveScope(agent.scope, scope);
3923
+ const basePath = getScopedBasePath(dir, effectiveScope);
3924
+ const agentsDir = join9(basePath, targetConfig.agentsDir);
3925
+ const agentPath = join9(agentsDir, agent.fileName);
3926
+ await mkdir2(agentsDir, { recursive: true });
3927
+ await writeFile2(agentPath, agent.content, "utf-8");
3928
+ }
3929
+ async function installContextFile(file, dir, targetPath) {
3930
+ const fullPath = join9(dir, targetPath);
3931
+ await mkdir2(dirname2(fullPath), { recursive: true });
3932
+ await writeFile2(fullPath, file.content, "utf-8");
3933
+ }
3934
+ async function installMemory(memory, dir) {
3935
+ for (const file of memory.files) {
3936
+ const filePath = join9(dir, file.relativePath);
3937
+ await mkdir2(dirname2(filePath), { recursive: true });
3938
+ await writeFile2(filePath, file.content, "utf-8");
3939
+ }
3940
+ }
3941
+ function resolveScope(originalScope, overrideScope) {
3942
+ if (overrideScope) return overrideScope;
3943
+ return originalScope;
3944
+ }
3945
+ function mapContextFilePath(sourcePath, target) {
3946
+ if (sourcePath in PRIMARY_CONTEXT_MAP) {
3947
+ return TARGET_CONFIGS[target].contextFilePath;
3948
+ }
3949
+ return null;
3950
+ }
3951
+ function getScopedBasePath(dir, scope) {
3952
+ return scope === "project" ? dir : homedir6();
3953
+ }
3954
+ async function fileExists(path) {
3955
+ try {
3956
+ await access2(path);
3957
+ return true;
3958
+ } catch {
3959
+ return false;
3960
+ }
3961
+ }
3962
+
3025
3963
  export {
3026
3964
  scanMcpConfigs,
3027
3965
  introspectServers,
3028
3966
  runAllScanners,
3029
- generateHtml
3967
+ generateHtml,
3968
+ ACI_DIR,
3969
+ ACI_BUNDLE,
3970
+ isSensitiveVar,
3971
+ detectEnvVars,
3972
+ exportEcosystem,
3973
+ detectTargetTools,
3974
+ loadBundle,
3975
+ planImport,
3976
+ executeImport
3030
3977
  };