@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
|
@@ -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-
|
|
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('
|
|
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 -
|
|
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>> ai-context-inspector</h1>
|
|
2621
2623
|
<div class="subtitle">${esc2(project.name)} — ${date} — ${scanDuration}ms</div>
|
|
2622
2624
|
<div class="badges">
|
|
2623
|
-
<
|
|
2624
|
-
<
|
|
2625
|
-
<
|
|
2626
|
-
<
|
|
2627
|
-
<
|
|
2628
|
-
<
|
|
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-
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
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
|
};
|