@cocaxcode/ai-context-inspector 0.3.3 → 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.
@@ -3030,9 +3030,948 @@ function escHtml(str) {
3030
3030
  return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
3031
3031
  }
3032
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
+
3033
3963
  export {
3034
3964
  scanMcpConfigs,
3035
3965
  introspectServers,
3036
3966
  runAllScanners,
3037
- generateHtml
3967
+ generateHtml,
3968
+ ACI_DIR,
3969
+ ACI_BUNDLE,
3970
+ isSensitiveVar,
3971
+ detectEnvVars,
3972
+ exportEcosystem,
3973
+ detectTargetTools,
3974
+ loadBundle,
3975
+ planImport,
3976
+ executeImport
3038
3977
  };